Java: почему нельзя перебрать итератор? - PullRequest
20 голосов
/ 08 апреля 2010

Я прочитал Почему итератор Java не является итерируемым? и Почему перечисления не итерируемы? , но я до сих пор не понимаю, почему это:

void foo(Iterator<X> it) {
  for (X x : it) {
    bar(x);
    baz(x);
  }
}

не было возможно.Другими словами, если я что-то упустил, вышеприведенное могло бы быть хорошим и правильным синтаксическим сахаром для:

void foo(Iterator<X> it) {
  for (X x; it.hasNext();) {
    x = it.next();
    bar(x);
    baz(x);
  }
}

Ответы [ 6 ]

19 голосов
/ 08 апреля 2010

Скорее всего, причина этого в том, что итераторы не могут быть использованы повторно;вам нужно получать новый Iterator из коллекции Iterable каждый раз, когда вы хотите выполнить итерации по элементам.Однако, в качестве быстрого решения:

private static <T> Iterable<T> iterable(final Iterator<T> it){
     return new Iterable<T>(){ public Iterator<T> iterator(){ return it; } };
}

//....
{
     // ...
     // Now we can use:
     for ( X x : iterable(it) ){
        // do something with x
     }
     // ...
}
//....

Тем не менее, лучше всего просто обойти интерфейс Iterable<T> вместо Iterator<T>

9 голосов
/ 08 апреля 2010

но я до сих пор не понимаю, почему это [...] не стало возможным.

Я вижу несколько причин:

  1. Iterator s не могут быть использованы повторно, поэтому for / each потребляет итератор - возможно, не некорректное поведение, но не интуитивно понятное для тех, кто не знает, как for / each обесценивается.
  2. Iterator s не выглядят "голыми" в коде все это часто, так что это будет усложнять JLS с небольшим усилением (конструкция for / each достаточно плоха, так как работает как с Iterable s, так и с массивы).
  3. Есть простой обходной путь . Может показаться немного расточительным выделение нового объекта только для этого, но выделение является дешевым, и анализ побега избавит вас даже от этой небольшой стоимости в большинстве случаев. (Однако почему я не включил этот обходной путь в служебный класс Iterables, аналогичный Collections и Arrays, мне неизвестно.)
  4. (Возможно, это не так - см. Комментарии.) Кажется, я вспоминаю, что JLS может ссылаться только на вещи в java.lang [цитата нужна] , поэтому им придется создать Iterator интерфейс в java.lang, который java.util.Iterator расширяется без добавления чего-либо. Теперь у нас есть два функционально эквивалентных интерфейса итератора. 50% нового кода, использующего обнаженные итераторы, выберут версию java.lang, остальные используют код в java.util. Возникает хаос, проблемы с совместимостью и т. Д.

Я думаю, что пункты 1-3 во многом соответствуют философии проектирования языка Java: не удивляйте новичков, не усложняйте спецификацию, если она не дает явного выигрыша, который затмевает затраты и не делайте с языковой функцией то, что можно сделать с библиотекой.

Те же аргументы объясняют, почему java.util.Enumeration тоже не Iterable.

6 голосов
/ 08 апреля 2010

Синтаксис for(Type t : iterable) действителен только для классов, которые реализуют Iterable<Type>.

Итератор не реализует итеративный.

Вы можете выполнять итерации по таким вещам, как Collection<T>, List<T> или Set<T>, поскольку они реализуют Iterable.

Следующий код эквивалентен:

for (Type t: list) {
    // do something with t
}

и

Iterator<Type> iter = list.iterator();
while (iter.hasNext()) {
    t = iter.next();
    // do something with t
}

Причина, по которой это не стало возможным,потому что синтаксис for-each был добавлен в язык для абстрагирования Iterator.Заставить цикл for-each работать с итераторами не получится, для чего был создан цикл for-each.

4 голосов
/ 29 июля 2015

На самом деле, вы можете.

В java 8 доступен очень короткий обходной путь:

for (X item : (Iterable<X>) () -> iterator)

См. Как выполнить итерацию с циклом foreach над потоком Java 8 для подробного объяснения уловки.

И некоторые объяснения, почему это не было изначально поддержано, можно найти в связанном вопросе:

Почему Stream не реализует Iterable ?

2 голосов
/ 08 апреля 2010

Итераторы не предназначены для повторного использования (т. Е. Используются более чем в одном цикле итерации).В частности, Iterator.hasNext() гарантирует, что вы можете безопасно вызвать Iterator.next() и действительно получить следующее значение из базовой коллекции.

Когда один и тот же итератор используется в двух одновременно работающих итерациях (предположим, многопоточностьсценарий), это обещание больше не может быть выполнено:

while(iter.hasNext() {
   // Now a context switch happens, another thread is performing
   //    iter.hasNext(); x = iter.next();

  String s = iter.next();  
          // A runtime exception is thrown because the iterator was 
          // exhausted by the other thread
}

Такие сценарии полностью нарушают протокол, предлагаемый Iterator.На самом деле они могут возникать даже в однопоточной программе: итерационный цикл вызывает другой метод, который использует тот же итератор для выполнения своей собственной итерации.Когда этот метод возвращается, вызывающая сторона выполняет вызов Iterator.next(), который снова завершается ошибкой.

0 голосов
/ 08 апреля 2010

Потому что for-each предназначен для чтения как что-то вроде:

for each element of [some collection of elements]

Iterator не [some collection of elements]. Массив и Iterable - это.

...