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);
}
}
Ответы
Ответ 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
есть.