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

Почему 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)" будет обрабатывать все элементы дважды (и забыть получить свежий итератор)? Или есть еще одна причина для этого?

Или есть какой-то трюк, который я просто не знаю?

4b9b3361

Ответ 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() ;
}

происходило все время в коде старого стиля. И делать причудливые вещи с несколькими итераторами этого не делали.