Есть ли способ получить доступ к счетчику итераций в цикле Java для каждого? - PullRequest
248 голосов
/ 25 января 2009

Есть ли способ в 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?

Ответы [ 16 ]

180 голосов
/ 25 января 2009

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

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

59 голосов
/ 29 сентября 2009

Есть другой способ.

Учитывая, что вы пишете свой собственный класс 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++); }
                    ...
                }
            }
        }
    }
}
41 голосов
/ 25 января 2009

Самое простое решение - это , чтобы просто запустить собственный счетчик следующим образом:

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

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

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

23 голосов
/ 05 апреля 2014

Использование lambdas и функциональных интерфейсов в 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);
      }
   }
}

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

16 голосов
/ 25 января 2009

Боюсь, это невозможно с foreach. Но я могу предложить вам простые старые петли for :

    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);
    }
15 голосов
/ 18 октября 2016

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
});
9 голосов
/ 25 января 2009

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

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

Это синтаксический сахар, но, очевидно, было сделано много запросов на эту функцию. Но до тех пор, пока он не будет одобрен, вам придется самостоятельно считать итерации или использовать обычный цикл for с Iterator.

5 голосов
/ 13 октября 2017

Идиоматическое решение:

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.

2 голосов
/ 10 января 2019

Хотя для достижения того же самого было упомянуто много других способов, я поделюсь своим путем для некоторых неудовлетворенных пользователей. Я использую функцию 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 голосов
/ 25 января 2009

Если вам нужен счетчик в цикле «для каждого», вы должны считать себя. Насколько я знаю, встроенного счетчика нет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...