Есть ли способ получить доступ к счетчику итераций в цикле 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++;
}

единственный способ иметь такой счетчик в цикле for-each?

Kosi2801
источник
2
Еще жаль, что вы не можете использовать переменную цикла вне цикла,Type var = null; for (var : set) dosomething; if (var != null) then ...
Val
@Val, если ссылка не является окончательной. Смотрите мой ответ о том, как использовать эту функцию
rmuller

Ответы:

205

Нет, но вы можете предоставить свой счетчик.

Причина этого заключается в том, что цикл for-each внутри не имеет счетчика; он основан на интерфейсе Iterable , то есть он использует Iteratorцикл для «коллекции», которая может вообще не быть коллекцией, а может фактически быть чем-то совсем не основанным на индексах (таких как связанный список).

Майкл Боргвардт
источник
5
У Руби есть конструкция для этого, и Java должна получить ее тоже ...for(int idx=0, String s; s : stringArray; ++idx) doSomethingWith(s, idx);
Николас ДиПиацца,
9
Ответ должен начинаться с «Нет, но вы можете указать свой счетчик».
user1094206 26.11.15
1
К вашему сведению @NicholasDiPiazza есть each_with_indexметод петли для Enumerableв Ruby. См. Apidock.com/ruby/Enumerable/each_with_index
скажем что теперь
@saywhatnow да, это то, что я имел в виду, извините - не уверен, что я курил, когда я сделал предыдущий комментарий
Николас ДиПьяцца
2
«Причина этого в том, что цикл for-each внутри не имеет счетчика». Следует читать так: «Java-разработчикам было все равно, чтобы программист мог указывать переменную индекса с начальным значением внутри forцикла», что-то вродеfor (int i = 0; String s: stringArray; ++i)
izogfif
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++); }
                    ...
                }
            }
        }
    }
}
akuhn
источник
7
Идеальная идея. Я бы проголосовал, если бы не создание недолговечных объектов класса Index.
mR_fr0g
3
@ mR_fr0g не волнуйтесь, я проверил это, и создание всех этих объектов не медленнее, чем повторное использование одного и того же объекта для каждой итерации. Причина в том, что все эти объекты расположены только в райском пространстве и никогда не живут достаточно долго, чтобы достичь кучи. Таким образом, их распределение происходит так же быстро, как, например, распределение локальных переменных.
Акун
1
@akuhn Подожди. Райское пространство не означает, что ГК не требуется. Наоборот, вам нужно вызвать конструктор, быстрее сканировать с помощью GC и завершить работу. Это не только загружает процессор, но и делает кэш недействительным все время. Ничего подобного не требуется для локальных переменных. Почему вы говорите, что это "то же самое"?
Val
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

это указывает на то, что порядок не считается характерной особенностью набора.

Есть и другие способы сделать это без ручного счетчика, но это немалый труд для сомнительной выгоды.

paxdiablo
источник
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); я ++; }
Маниус
21

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

final AtomicInteger indexHolder = new AtomicInteger();
map.forEach((k, v) -> {
    final int index = indexHolder.getAndIncrement();
    // use the index
});
rmuller
источник
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();
    }

Обратите внимание, что интерфейс List предоставляет вам доступ к it.nextIndex().

(редактировать)

К вашему измененному примеру:

    for(ListIterator<String> it =l.listIterator(); it.hasNext() ;)
    {
        int i = it.nextIndex();
        doSomethingWith(it.next(), i);
    }
Бруно Конде
источник
9

Одним из рассматриваемых изменений Sunявляется Java7предоставление доступа к внутреннему Iteratorв циклах foreach. синтаксис будет примерно таким (если это будет принято):

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

Это синтаксический сахар, но, очевидно, было сделано много запросов на эту функцию. Но до тех пор, пока он не будет одобрен, вам придется самостоятельно считать итерации или использовать регулярный цикл for с символом 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 предлагает в обсуждении в Гуаве, почему они не предоставили 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

Если вам нужен счетчик в цикле for-each, вы должны считать себя. Там нет встроенного счетчика, насколько я знаю.

EricSchaefer
источник
(Я исправил эту ошибку в вопросе.)
Том Хотин - tackline
1

Есть вариант ответа pax ... ;-)

int i = -1;
for(String s : stringArray) {
    doSomethingWith(s, ++i);
}
Йорг
источник
3
Любопытно, есть ли польза от этого по сравнению с, казалось бы, более четким i=0; i++;подходом?
Джошуа Пинтер
1
Я думаю, если вам нужно снова использовать индекс i после doSomething в области видимости.
Гседо
1

В ситуациях, когда мне нужен только индекс, например, в предложении catch, я иногда буду использовать 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 () объектов Arrays, которые могут однозначно идентифицировать определенный объект. Это не относится к строкам, если определенная строка находится в массиве несколько раз, вы получите только индекс первого вхождения, даже если вы уже перебирали более поздний элемент с той же строкой.
Kosi2801
-1

Лучшее и оптимизированное решение - сделать следующее:

int i=0;

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

Где Тип может быть любым типом данных, а тип - это переменная, к которой вы применяете цикл.

Абхиджит Чопра
источник
Пожалуйста, предоставьте свой ответ в формате воспроизводимого кода.
Нареман Дарвиш
Это именно тот путь, для которого требуется альтернативное решение. Речь идет не о типах, а о возможностях доступа к счетчику циклов РАЗНЫМ способом, чем счет вручную.
Kosi2801
-4

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

Примечание: это предполагает, что элементы списка уникальны или что не имеет значения, не являются ли они неуникальными (потому что в этом случае будет возвращен индекс первой найденной копии).

Есть ситуации, в которых этого было бы достаточно ...

Rik
источник
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); 


    }
}
ashkanaral
источник
1
Извините, это не ответ на вопрос. Речь идет не о доступе к контенту, а о том, как часто цикл уже повторяется.
Kosi2801
Вопрос заключался в том, чтобы узнать текущий индекс в цикле в цикле для каждого стиля.
rumman0786