Подтвердить что ты не робот

Java: почему нельзя перебирать итератор?

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

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);
  }
}
4b9b3361

Ответ 1

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

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

  • Iterator не могут быть повторно использованы, поэтому для каждого из них будет использоваться итератор, а не неправильное поведение, возможно, но неинтуитивное для тех, кто не знает, как отменить для каждого из них.
  • Iterator не всегда выглядят "голыми" в коде, поэтому это усложняет JLS с небольшим усилением (конструкция for/each достаточно плоха, как есть, работает как на Iterable, так и на массивах).
  • Там удобный способ обхода. Может показаться немного расточительным для размещения нового объекта только для этого, но распределение дешево, так как это и анализ побега избавит вас даже от этой небольшой стоимости в большинстве случаев. (Почему они не включили этот обходной путь в класс утилиты Iterables, аналогичный Collections и Arrays, хотя и вне меня.)
  • (Вероятно, неверно - см. комментарии.) Я, кажется, помню, что JLS может ссылаться только на вещи в java.lang [citation needed] поэтому им нужно будет создать интерфейс Iterator в java.lang, который java.util.Iterator распространяется без добавления чего-либо. Теперь у нас есть два функционально эквивалентных интерфейса итератора. 50% нового кода, использующего голые итераторы, выберет версию java.lang, а остальная часть - в java.util. Наступает хаос, проблемы с совместимостью изобилуют и т.д.

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

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

Ответ 2

Скорее всего, причина в том, что итераторы не могут использоваться повторно; вам нужно получить новый 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>

Ответ 3

Синтаксис 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
}

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

Ответ 4

Собственно, вы можете.

В java 8 очень короткое обходное решение:

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

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

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

Почему Stream <T> не реализовать Iterable <T> ?

Ответ 5

Итераторы не предназначены для повторного использования (т.е.: используются в нескольких циклах итерации). В частности, 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(), который, опять же, терпит неудачу.

Ответ 6

Поскольку каждый из них предназначен для чтения как-то вроде:

for each element of [some collection of elements]

An Iterator не [some collection of elements]. Массив и Iterable есть.