Почему Java не разрешает foreach на итераторах (только на итерациях)?
Возможный дубликат:
Почему Java Iterator не является итерабельным?
Идиоматический способ использования для каждого цикла, заданного итератором?
Можно ли использовать цикл for для каждого итерации объектов типа Iterator?
Цикл foreach - это насколько я знаю, что в Java добавлен синтаксический сахар. Итак
Iterable<O> iterable;
for(O o : iterable) {
// Do something
}
будет по существу производить тот же байт-код, что и
Iterable<O> iterable;
for(Iterator<O> iter = iterable.iterator(); iter.hasNext(); /* NOOP */) {
O o = iter.next();
// Do something
}
Однако, если у меня нет итерации в первую очередь, а только итератора (скажем, потому что класс предлагает два разных итератора), я не могу использовать синтаксический цикл foreach. Очевидно, я все еще могу выполнить обычную старую итерацию стиля. Тем не менее, я бы действительно хотел:
Iterator<O> iter;
for(O o : iter /* Iterator<O>, not Iterable<O>! */) {
// Do something
}
И, конечно, я могу сделать подделку Iterable
:
class Adapter<O> implements Iterable<O> {
Iterator<O> iter;
public Adapter(Iterator<O> iter) {
this.iter = iter;
}
@Override
public Iterator<O> iterator() {
return iter;
}
}
(что на самом деле является уродливым злоупотреблением API-интерфейсом Iterable, поскольку его можно только повторить один раз!)
Если он был создан вокруг Iterator
вместо iterable, можно было бы сделать несколько интересных вещей:
for(O o : iterable.iterator()) {} // Iterate over Iterable and Collections
for(O o : list.backwardsIterator()) {} // Or backwards
Iterator<O> iter;
for(O o : iter) {
if (o.something()) { iter.remove(); }
if (o.something()) { break; }
}
for(O : iter) { } // Do something with the remaining elements only.
Кто-нибудь знает, почему язык был разработан таким образом? Чтобы избежать двусмысленности, если класс будет реализовывать как Iterator
, так и Iterable
? Чтобы избежать ошибок программиста, предполагающих, что "for (O o: iter)" будет обрабатывать все элементы дважды (и забыть получить свежий итератор)? Или есть еще одна причина для этого?
Или есть какой-то трюк, который я просто не знаю?
Ответы
Ответ 1
Итак, теперь у меня есть несколько разумное объяснение:
Короткая версия: Поскольку синтаксис также применяется к массивам, у которых нет итераторов.
Если синтаксис был спроектирован вокруг Iterator
, как я предложил, он будет несовместим с массивами. Позвольте мне дать три варианта:
A), выбранный разработчиками Java:
Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list) { }
Iterator<Object> iter;
while(iter.hasNext()) { Object o = iter.next(); }
Ведет себя так же и очень согласован между массивами и коллекциями.
Итераторы, однако, должны использовать классический стиль итерации (который, по крайней мере, не вызывает ошибок).
B) разрешить массивы и Iterators
:
Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list.iterator()) { }
Iterator<Object> iter;
for(Object o : iter) { }
Теперь массивы и коллекции несовместимы; но массивы и ArrayList очень тесно связаны и должны вести себя одинаково. Теперь, если в любой момент язык расширяется, чтобы сделать, например, массивы реализуют Iterable
, он становится непоследовательным.
C) разрешить все три:
Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list) { }
Iterator<Object> iter;
for(Object o : iter) { }
Теперь, если мы окажемся в неясных ситуациях, когда либо кто-то реализует как Iterable
, так и Iterator
(это цикл for, который должен получить новый итератор или перебирать текущее - происходит легко в древовидных структурах!?!). К сожалению, простой tie-braker ala "Iterable beats Iterator" не будет делать: он неожиданно вводит время выполнения и разницу во времени компиляции и генерические проблемы.
Теперь неожиданно нам нужно обратить внимание на то, хотим ли мы итерации по коллекциям/итерам или массивам, и в этот момент мы получили очень мало преимуществ за счет большой путаницы.
Способ "для каждого" находится в Java (A) очень согласован, он вызывает очень мало ошибок программирования, и это позволяет возможное будущее изменение поворота массивов в обычные объекты.
Существует вариант D), который, вероятно, также будет работать нормально:
для каждого для итераторов. Предпочтительно, добавив метод .iterator()
к примитивным массивам:
Object[] array;
for(Object o : array.iterator()) { }
Iterable<Object> list;
for(Object o : list.iterator()) { }
Iterator<Object> iter;
for(Object o : iter) { }
Но для этого требуются изменения среды выполнения, а не только компилятора, и прерывания обратной совместимости. Кроме того, упомянутая путаница все еще присутствует, что
Iterator<Object> iter;
for(Object o : iter) { }
for(Object o : iter) { }
Выполняется только однократное повторение данных.
Ответ 2
Кто-нибудь знает, почему язык был разработан таким образом?
Потому что for-each имеет смысл только для вещей, которые являются итеративными, и не имеет смысла для итераторов. Если у вас уже есть итератор, у вас уже есть то, что вам нужно, чтобы сделать это с помощью простого цикла.
Сравните: я начинаю с повторяемого:
// Old way
Iterator<Thingy> it = iterable.iterator();
while (it.hasNext()) {
Thingy t = it.next();
// Use 't'
}
// New way
for (Thingy t : iterable) {
// Use 't'
}
По сравнению с тем, что я начинаю с итератора:
// Old/current way
while (iterator.hasNext()) {
Thing t = iterator.next();
// Use 't'
}
// Imagined way
for (Thingy t : iterator) {
// Use 't'
}
Во втором примере этого не так много, и это усложняет семантику для каждого, создавая особый случай.
Вопросы "почему" всегда трудны, когда они не направлены на основных участников, вовлеченных в решение, но я предполагаю, что дополнительная сложность не стоила предельной полезности.
Тем не менее, я мог видеть "расширенной в while
петля" конструкция:
while (Thingy t : iterator) {
// Use 't'
}
... который определяет, где сейчас находится итератор... Мех, может быть, это слишком запутает людей. :-)
Ответ 3
Интерфейс Iterable был создан именно для этой цели (расширен для цикла), как описано в оригинальной JSR, хотя интерфейс Iterator уже использовался.
Что касается новых интерфейсов, обсуждаемых в JSR (обратите внимание на имена пакетов):
java.lang.Iterable
java.lang.ReadOnlyIterator
(предлагается в JSR для дооснащения java.util.Iterator
, хотя фактически этого не сделано)
... JSR говорит:
Эти новые интерфейсы служат для предотвращения зависимости языка от java.util
, которая в противном случае могла бы возникнуть.
Ответ 4
Потому что цикл "for" будет разрушительным для итератора. Итератор не может быть reset (т.е. возвращен в начало), если он не реализует подчиненный интерфейс ListIterator.
Как только вы установите Iterator через цикл "for", он больше не будет использоваться. Я предполагаю, что разработчики языка решили, что это в сочетании с дополнительными специальными случаями (из которых уже есть два для Iterable и массивов) в компиляторе, чтобы перевести это в байт-код (вы не могли повторно использовать одно и то же преобразование как итеративный), было достаточно хулитель, чтобы не реализовать его.
Когда вы сделаете это самостоятельно в коде через интерфейс итератора, он будет по крайней мере уверенным, что происходит.
С приходом lambdas они могут сделать это красивым и легким:
Iterator<String> iterator = ...;
Collections.each ( iterator, (String s) => { System.out.println(s); } );
List<String> list = ...;
Collections.each ( list, (String s) => { System.out.println(s); } );
не нарушая обратной совместимости и все еще имея относительно простой синтаксис. Я сомневаюсь, что они построили такие методы, как "каждый", "собрать" и "отобразить" на разные интерфейсы, потому что это сломало бы обратную совместимость, плюс у вас еще были бы массивы.
Ответ 5
Я думаю, что одна часть ответа может быть скрыта в том, что для каждого цикла используется синтаксический сахар. Дело в том, что вы хотите сделать что-то, что люди делают много, намного проще. И (по крайней мере, по моему опыту) идиома
Iterator iterator = iterable.iterator();
while( iterator.hasNext() ) {
Element e = (Element)iterator.next() ;
}
происходило все время в коде старого стиля. И делать причудливые вещи с несколькими итераторами этого не делали.