Чи є спосіб отримати доступ до лічильника ітерацій в Java для кожного циклу?


274

Чи існує спосіб у Java для кожного циклу

for(String s : stringArray) {
  doSomethingWith(s);
}

щоб дізнатися, як часто цикл вже обробляється?

Крім використання старого і відомого for(int i=0; i < boundary; i++)- циклу, є конструкція

int i = 0;
for(String s : stringArray) {
  doSomethingWith(s);
  i++;
}

єдиний спосіб мати такий лічильник доступний для кожного циклу?


2
Ще шкода, що ви не можете використовувати змінну циклу поза циклом,Type var = null; for (var : set) dosomething; if (var != null) then ...
Val

@ Val, якщо посилання не є ефективним остаточним. Дивіться мою відповідь щодо використання цієї функції
rmuller

Відповіді:


205

Ні, але ви можете надати власний лічильник.

Причина цього полягає в тому , що для кожної петля-внутрішньо не має лічильника; він заснований на інтерфейсі Iterable , тобто він використовує Iteratorцикл через "колекцію" - що може взагалі не бути колекцією, а насправді може бути чимось зовсім не заснованим на індексах (наприклад, пов'язаний список).


5
У Рубі є конструкція для цього, і Java повинна отримати це теж ...for(int idx=0, String s; s : stringArray; ++idx) doSomethingWith(s, idx);
Nicholas DiPiazza

9
Відповідь повинна починатися з "Ні, але ви можете надати власний лічильник".
user1094206

1
FYI @NicholasDiPiazza існує each_with_indexметод циклу для EnumerableRuby. Дивіться apidock.com/ruby/Enumerable/each_with_index
сказати, що зараз

@saywhatnow Так, це те, що я мав на увазі вибачте - не впевнений, що я курив, коли я зробив попередній коментар
Nicholas DiPiazza

2
"Причиною цього є те, що для кожного циклу внутрішньо немає лічильника". Слід читати як "розробники Java не хвилювались, щоб програміст forfor (int i = 0; String s: stringArray; ++i)
вказував

64

Є й інший спосіб.

Враховуючи, що ви пишете власний Indexклас та статичний метод, який повертає Iterableзайві екземпляри цього класу, які ви можете

for (Index<String> each: With.index(stringArray)) {
    each.value;
    each.index;
    ...
}

Де реалізація With.indexчимось схожа

class With {
    public static <T> Iterable<Index<T>> index(final T[] array) {
        return new Iterable<Index<T>>() {
            public Iterator<Index<T>> iterator() {
                return new Iterator<Index<T>>() {
                    index = 0;
                    public boolean hasNext() { return index < array.size }
                    public Index<T> next() { return new Index(array[index], index++); }
                    ...
                }
            }
        }
    }
}

7
Акуратна ідея. Я б відмовився, якби не створення короткотривалих об'єктів класу Index.
mR_fr0g

3
@ mR_fr0g не хвилюйся, я зробив це за те, і створення цих об'єктів не повільніше, ніж повторне використання одного і того ж об'єкта для кожної ітерації. Причина полягає в тому, що всі ці об'єкти розподіляються лише в одному просторі і ніколи не існують достатньо довго, щоб досягти купи. Тому розподіл їх відбувається так само швидко, як, наприклад, розподіл локальних змінних.
акун

1
@akuhn Зачекай. Простір Ідею не означає, що жоден GC не придбаний. Зовсім навпаки, потрібно викликати конструктор, скоріше сканувати за допомогою GC та доопрацювати. Це не тільки завантажує процесор, але й постійно скасовує кеш. Нічого подібного не потрібно для локальних змінних. Чому ти кажеш, що це "те саме"?
Валь,

7
Якщо ви турбуєтесь про виконання таких короткотривалих об’єктів, ви робите це неправильно. Продуктивність повинна бути ОСТАНОЮ, про яку слід побоюватися ... читабельність першою. Це насправді досить гарна конструкція.
Білл К

4
Я збирався сказати, що насправді не розумію необхідності обговорювати, коли екземпляр Index можна створити один раз і повторно використати / оновити, просто щоб обґрунтувати аргумент і зробити всіх щасливими. Однак у моїх вимірах версія, яка створює новий Index (), щоразу виконується на моїй машині більше ніж удвічі швидше, приблизно рівна народному ітератору, який виконує ті самі ітерації.
Ерік Вудруф

47

Найпростішим рішенням є просто запустити свій власний лічильник , таким чином:

int i = 0;
for (String s : stringArray) {
    doSomethingWith(s, i);
    i++;
}

Причиною цього є те, що немає фактичної гарантії, що елементи колекції (над якими цей варіант forповторюється) навіть мають індекс, або навіть мають певний порядок (деякі колекції можуть змінювати порядок, коли ви додаєте або видаляєте елементи).

Дивіться, наприклад, наступний код:

import java.util.*;

public class TestApp {
  public static void AddAndDump(AbstractSet<String> set, String str) {
    System.out.println("Adding [" + str + "]");
    set.add(str);
    int i = 0;
    for(String s : set) {
        System.out.println("   " + i + ": " + s);
        i++;
    }
  }

  public static void main(String[] args) {
    AbstractSet<String> coll = new HashSet<String>();
    AddAndDump(coll, "Hello");
    AddAndDump(coll, "My");
    AddAndDump(coll, "Name");
    AddAndDump(coll, "Is");
    AddAndDump(coll, "Pax");
  }
}

Запустивши це, ви можете побачити щось на кшталт:

Adding [Hello]
   0: Hello
Adding [My]
   0: Hello
   1: My
Adding [Name]
   0: Hello
   1: My
   2: Name
Adding [Is]
   0: Hello
   1: Is
   2: My
   3: Name
Adding [Pax]
   0: Hello
   1: Pax
   2: Is
   3: My
   4: Name

вказуючи на те, що, справедливо так, порядок не вважається помітною ознакою набору.

Є й інші способи зробити це без ручного лічильника, але це сумнівна робота для сумнівної вигоди.


2
Це все ще здається найяскравішим рішенням навіть для інтерфейсів List та Iterable.
Джошуа Пінтер

27

Використання лямбдасів та функціональних інтерфейсів у Java 8 робить можливим створення нових абстракцій циклу. Я можу перейти до колекції з індексом та розміром колекції:

List<String> strings = Arrays.asList("one", "two","three","four");
forEach(strings, (x, i, n) -> System.out.println("" + (i+1) + "/"+n+": " + x));

Які виходи:

1/4: one
2/4: two
3/4: three
4/4: four

Що я реалізував як:

   @FunctionalInterface
   public interface LoopWithIndexAndSizeConsumer<T> {
       void accept(T t, int i, int n);
   }
   public static <T> void forEach(Collection<T> collection,
                                  LoopWithIndexAndSizeConsumer<T> consumer) {
      int index = 0;
      for (T object : collection){
         consumer.accept(object, index++, collection.size());
      }
   }

Можливості безмежні. Наприклад, я створюю абстракцію, яка використовує спеціальну функцію саме для першого елемента:

forEachHeadTail(strings, 
                (head) -> System.out.print(head), 
                (tail) -> System.out.print(","+tail));

Який надрукує список, відокремлений комами:

one,two,three,four

Що я реалізував як:

public static <T> void forEachHeadTail(Collection<T> collection, 
                                       Consumer<T> headFunc, 
                                       Consumer<T> tailFunc) {
   int index = 0;
   for (T object : collection){
      if (index++ == 0){
         headFunc.accept(object);
      }
      else{
         tailFunc.accept(object);
      }
   }
}

Бібліотеки почнуть з'являтися, щоб робити подібні речі, або ви можете скочувати свої власні.


3
Я не критикую повідомлення (я думаю, що "це крутий спосіб зробити це" в наші дні), але я намагаюся зрозуміти, як це простіше / краще, ніж звичайна стара школа для циклу: for (int i = 0; i < list.size (); i ++) {} Навіть бабуся могла це зрозуміти, і це, мабуть, простіше набрати без синтаксису та незвичайних символів, які використовує лямбда. Не зрозумійте мене неправильно, я люблю використовувати лямбда для певних речей (тип шаблону обробника викликів / подій), але я просто не можу зрозуміти використання у таких випадках. Чудовий інструмент, але використовуючи його постійно , я просто не можу зробити.
Маній

Я гадаю, деяке уникнення переліку / підточки індексу "за одним" проти самого списку. Але є варіант, розміщений вище: int i = 0; for (String s: stringArray) {doSomethingWith (s, i); i ++; }
Маній

21

Java 8 представила метод Iterable#forEach()/ Map#forEach(), який є більш ефективним для багатьох Collection/ Mapреалізацій порівняно з "класичним" для кожного циклу. Однак і в цьому випадку індекс не надається. Хитрість тут полягає у використанні AtomicIntegerпоза лямбда-виразу. Примітка: змінні, що використовуються в лямбда-виразі, повинні бути ефективно остаточними, тому ми не можемо використовувати звичайний int.

final AtomicInteger indexHolder = new AtomicInteger();
map.forEach((k, v) -> {
    final int index = indexHolder.getAndIncrement();
    // use the index
});

16

Боюся, що це неможливо foreach. Але я можу запропонувати вам простий старий стиль для циклів :

    List<String> l = new ArrayList<String>();

    l.add("a");
    l.add("b");
    l.add("c");
    l.add("d");

    // the array
    String[] array = new String[l.size()];

    for(ListIterator<String> it =l.listIterator(); it.hasNext() ;)
    {
        array[it.nextIndex()] = it.next();
    }

Зверніть увагу, що інтерфейс списку надає вам доступ до it.nextIndex().

(редагувати)

До зміненого прикладу:

    for(ListIterator<String> it =l.listIterator(); it.hasNext() ;)
    {
        int i = it.nextIndex();
        doSomethingWith(it.next(), i);
    }

9

Однією із змін Sun, які розглядаються, Java7є надання доступу до внутрішніх Iteratorу передній петлі. синтаксис буде приблизно таким (якщо це прийнято):

for (String str : list : it) {
  if (str.length() > 100) {
    it.remove();
  }
}

Це синтаксичний цукор, але, мабуть, було багато запитів щодо цієї функції. Але поки його не буде затверджено, вам доведеться самостійно рахувати ітерації або використовувати звичайний цикл з an Iterator.


7

Ідіоматичне рішення:

final Set<Double> doubles; // boilerplate
final Iterator<Double> iterator = doubles.iterator();
for (int ordinal = 0; iterator.hasNext(); ordinal++)
{
    System.out.printf("%d:%f",ordinal,iterator.next());
    System.out.println();
}

це насправді рішення, яке Google пропонує в дискусії Guava про те, чому вони не запропонували CountingIterator.


4

Хоча є багато інших способів, згаданих для досягнення цього, я поділюсь своїм незадоволеним користувачам. Я використовую функцію Java 8 IntStream.

1. Масиви

Object[] obj = {1,2,3,4,5,6,7};
IntStream.range(0, obj.length).forEach(index-> {
    System.out.println("index: " + index);
    System.out.println("value: " + obj[index]);
});

2. Список

List<String> strings = new ArrayList<String>();
Collections.addAll(strings,"A","B","C","D");

IntStream.range(0, strings.size()).forEach(index-> {
    System.out.println("index: " + index);
    System.out.println("value: " + strings.get(index));
});

2

Якщо вам потрібен лічильник у кожному циклі, вам доведеться порахувати себе. Наскільки я не знаю, немає вбудованого лічильника.


(Я виправив цю помилку у питанні.)
Том Хоутін - таклін

1

Існує "варіант" відповіді "pax" ... ;-)

int i = -1;
for(String s : stringArray) {
    doSomethingWith(s, ++i);
}

3
Цікаво, чи є користь від використання цього, начебто, більш зрозумілого i=0; i++;підходу?
Джошуа Пінтер

1
Я думаю, якщо вам потрібно знову використовувати індекс i після doSomething в області для області.
gcedo

1

У ситуаціях, коли мені потрібен індекс лише час від часу, як, наприклад, у пункті вилову, я інколи буду використовувати indexOf.

for(String s : stringArray) {
  try {
    doSomethingWith(s);
  } catch (Exception e) {
    LOGGER.warn("Had some kind of problem with string " +
      stringArray.indexOf(s) + ": " + s, e);
  }
}

2
Будьте уважні, що для цього потрібна .equals () - реалізація об'єктів масивів, які можуть однозначно ідентифікувати певний об'єкт. Це не стосується рядків, якщо певна рядок є в масиві кілька разів, ви отримаєте лише індекс першого виникнення, навіть якщо ви вже повторювали наступний елемент з тією ж строкою.
Kosi2801

-1

Найкращим і оптимізованим рішенням є наступне:

int i=0;

for(Type t: types) {
  ......
  i++;
}

Де Type може бути будь-який тип даних і типів - це змінна, за якою ви подаєте заявку на цикл.


Будь ласка, надайте свою відповідь у відтворюваному форматі коду.
Нареман Дарвіш

Саме такий спосіб шукає альтернативне рішення. Питання полягає не в типах, а в можливостях отримати доступ до лічильника циклу різними способами, ніж підрахунок вручну.
Kosi2801

-4

Я трохи здивований, що ніхто не запропонував такого (я визнаю, це лінивий підхід ...); Якщо stringArray - це якийсь Список, ви можете використовувати щось на зразок stringArray.indexOf (S), щоб повернути значення для поточного підрахунку.

Примітка. Це передбачає, що елементи Списку є унікальними або не має значення, чи не є унікальними (тому що в цьому випадку він поверне індекс першої знайденої копії).

Є ситуації, в яких цього було б достатньо ...


9
З ефективністю, що наближається до O (n²) для всього циклу, дуже важко уявити навіть кілька ситуацій, коли це було б кращим за все інше. Вибачте.
Kosi2801

-5

Ось приклад того, як я це зробив. Це отримує індекс на циклі для кожного циклу. Сподіваюсь, це допомагає.

public class CheckForEachLoop {

    public static void main(String[] args) {

        String[] months = new String[] { "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", "JULY", "AUGUST",
                "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER" };
        for (String s : months) {
            if (s == months[2]) { // location where you can change
              doSomethingWith(s); // however many times s and months
                                  // doSomethingWith(s) will be completed and 
                                  // added together instead of counter
            }

        }
        System.out.println(s); 


    }
}

1
Вибачте, це не відповідає на питання. Йдеться не про доступ до контенту, а про лічильник того, як часто цикл вже був повторений.
Kosi2801

Питання полягало в тому, щоб з'ясувати поточний індекс у циклі для циклу стилів для кожного.
rumman0786
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.