Почему итератор Java не является итерируемым?


178

Почему Iteratorинтерфейс не расширяется Iterable?

iterator()Метод может просто вернуться this.

Это специально или просто недосмотр разработчиков Java?

Было бы удобно иметь возможность использовать цикл for-each с итераторами:

for(Object o : someContainer.listSomeObjects()) {
    ....
}

где listSomeObjects()возвращает итератор.


1
Хорошо, я вижу вашу точку зрения. все равно было бы удобно, даже если бы он немного нарушил семантику:] Спасибо U за все ответы:]
Łukasz Bownik

Я понимаю, что этот вопрос задавался давно, но - вы имеете в виду какой-либо итератор или просто итератор, относящийся к некоторой коллекции?
einpoklum

Ответы:


67

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


25
+1: коллекция повторяется. Итератор не повторяется, потому что это не коллекция.
S.Lott

50
Хотя я согласен с ответом, я не знаю, согласен ли я с менталитетом. Интерфейс Iterable представляет один метод: Iterator <?> Iterator (); В любом случае, я должен иметь возможность указать итератор для каждого. Я не покупаю это.
Крис К

25
@ S.Лотт хорошие круговые рассуждения там.
yihtserns

16
@ S.Lott Последняя попытка: Collection ∈ Iterable. Итератор ≠ Коллекция ∴ Итератор ∉ Итерируемый
yihtserns

27
@ S.Lott: Коллекция совершенно не имеет отношения к этой дискуссии. Коллекция - только одна из многих возможных реализаций Iterable. Тот факт, что что-то не является коллекцией, не имеет отношения к тому, является ли она итерируемой.
ColinD

218

Итератор с состоянием. Идея состоит в том, что если вы Iterable.iterator()дважды позвоните, вы получите независимые итераторы - в любом случае, для большинства итераций. Это явно не будет иметь место в вашем сценарии.

Например, я обычно могу написать:

public void iterateOver(Iterable<String> strings)
{
    for (String x : strings)
    {
         System.out.println(x);
    }
    for (String x : strings)
    {
         System.out.println(x);
    }
}

Это должно напечатать коллекцию дважды - но с вашей схемой второй цикл всегда завершится мгновенно.


17
@Chris: Если реализация возвращает один и тот же итератор дважды, как же она может выполнить контракт итератора? Если вы вызываете iteratorи используете результат, он должен выполнить итерацию по коллекции, чего не произойдет, если этот же объект уже прошел итерацию по коллекции. Можете ли вы дать какую-либо правильную реализацию (кроме пустой коллекции), где один и тот же итератор возвращается дважды?
Джон Скит

7
Это отличный ответ, Джон, у тебя действительно есть суть проблемы здесь. Позор, это не принятый ответ! Контракт для Iterable строго определен, но вышеприведенное является отличным объяснением причин, по которым позволить Iterator реализовать Iterable (для foreach) нарушит дух интерфейса.
joelittlejohn

3
@JonSkeet, в то время как Iterator <T> является состоящим из состояний, в контракте Iterable <T> ничего не говорится о возможности его использования дважды для получения независимых итераторов, даже если это сценарий в 99% случаев. Все, что говорит Iterable <T>, - это то, что он позволяет объекту быть целью foreach. Для тех из вас, кто недоволен, что Iterator <T> не является Iterable <T>, вы можете сделать такой Iterator <T>. Это не нарушит контракт. Однако сам итератор не должен быть итерируемым, поскольку это сделало бы его циклически зависимым, и это заложило бы основу для неприглядного дизайна.
Centril

2
@Centril: Верно. Отредактировал, чтобы указать, что обычно вызов iterableдважды даст вам независимые итераторы.
Джон Скит

2
Это действительно доходит до сути. Было бы почти возможно реализовать Iterable Iterator, который реализует .iterator () путем сброса самого себя, но этот дизайн все еще сломался бы в некоторых обстоятельствах, например, если бы он был передан методу, который принимает Iterable и перебирает все возможные пары элементов путем вложения для каждого цикла.
Теодор Мердок

60

За мои $ 0,02 я полностью согласен с тем, что Iterator не должен реализовывать Iterable, но я думаю, что расширенный цикл for также должен принять. Я думаю, что весь аргумент «сделай итераторы итеративными» подходит для обхода дефекта в языке.

Основная причина введения расширенного цикла for заключалась в том, что он «устраняет трудоемкость и подверженность ошибкам итераторов и индексных переменных при переборе коллекций и массивов» [ 1 ].

Collection<Item> items...

for (Iterator<Item> iter = items.iterator(); iter.hasNext(); ) {
    Item item = iter.next();
    ...
}

for (Item item : items) {
    ...
}

Почему же этот аргумент не подходит для итераторов?

Iterator<Iter> iter...
..
while (iter.hasNext()) {
    Item item = iter.next();
    ...
}

for (Item item : iter) {
    ...
}

В обоих случаях вызовы hasNext () и next () были удалены, и во внутреннем цикле нет ссылки на итератор. Да, я понимаю, что Iterables можно повторно использовать для создания нескольких итераторов, но все это происходит вне цикла for: внутри цикла всегда имеется только прямая прогрессия по одному элементу за раз по сравнению с элементами, возвращаемыми итератором.

Кроме того, разрешение этого также облегчит использование цикла for для перечислений, которые, как было указано в других местах, аналогичны итераторам, а не итерации.

Так что не заставляйте Iterator реализовывать Iterable, но обновите цикл for, чтобы принять либо.

Ура,


6
Я согласен. Теоретически может возникнуть путаница при получении итератора, использовании его части и последующем размещении его в foreach (нарушении «каждого» контракта foreach), но я не думаю, что это достаточно веская причина для отсутствия этой функции.
Барт ван Хейкелом

Мы уже обновили / усовершенствовали цикл для принятия массивов вместо того, чтобы делать массивы повторяемыми коллекциями. Можете ли вы обосновать это решение?
Вэл

Я проголосовал за этот ответ, и это была ошибка. Итератор с состоянием, если вы продвигаете итерацию через итератор с for(item : iter) {...}синтаксисом, то он вызовет ошибку, когда один и тот же итератор будет повторен дважды. Представьте, что Iteratorпередается в iterateOverметод, а не Iterableв этом примере .
Илья Сильвестров

2
На самом деле не имеет значения, используете ли вы стиль for (String x : strings) {...}или while (strings.hasNext()) {...}: если вы попытаетесь дважды повторить итератор во второй раз, вы не получите результатов, поэтому я не рассматриваю это как аргумент против использования расширенного синтаксиса. Ответ Джона другой, потому что он показывает, как завершение Iteratorв Iterableможет вызвать проблемы, так как в этом случае вы могли бы использовать его столько раз, сколько захотите.
Барни

17

Как указывают другие, Iteratorи Iterableэто две разные вещи.

Кроме того, Iteratorреализации предшествуют усовершенствованным циклам.

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

for (String line : in(lines)) {
  System.out.println(line);
}

Пример реализации:

  /**
   * Adapts an {@link Iterator} to an {@link Iterable} for use in enhanced for
   * loops. If {@link Iterable#iterator()} is invoked more than once, an
   * {@link IllegalStateException} is thrown.
   */
  public static <T> Iterable<T> in(final Iterator<T> iterator) {
    assert iterator != null;
    class SingleUseIterable implements Iterable<T> {
      private boolean used = false;

      @Override
      public Iterator<T> iterator() {
        if (used) {
          throw new IllegalStateException("SingleUseIterable already invoked");
        }
        used = true;
        return iterator;
      }
    }
    return new SingleUseIterable();
  }

В Java 8 адаптация Iteratorк a Iterableстановится проще:

for (String s : (Iterable<String>) () -> iterator) {

вы объявили класс в функции; я что-то упускаю? Я думал, что это незаконно.
activedecay

Класс может быть определен в блоке в Java. Это называется местный класс
Колин Д. Беннетт

3
Спасибо заfor (String s : (Iterable<String>) () -> iterator)
Алик Эльзин-килака

8

Как уже говорили другие, Iterable может вызываться несколько раз, возвращая новый Iterator при каждом вызове; Итератор используется только один раз. Таким образом, они связаны, но служат различным целям. Однако, к сожалению, метод «компактный для» работает только с итерацией.

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

Хитрость заключается в том, чтобы вернуть анонимную реализацию Iterable, которая фактически запускает работу. Таким образом, вместо того, чтобы выполнять работу, которая генерирует одноразовую последовательность, а затем возвращать Итератор, вы возвращаете Итерируемый, который при каждом обращении к нему повторяет работу. Это может показаться расточительным, но часто вы все равно будете вызывать Iterable только один раз, и даже если вы вызываете его несколько раз, он все еще имеет разумную семантику (в отличие от простой оболочки, которая делает Iterator «похожим» на Iterable, это выиграет » не удается, если используется дважды).

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

class MetricDao {
    ...
    /**
     * @return All known metrics.
     */
    public final Iterable<Metric> loadAll() {
        return new Iterable<Metric>() {
            @Override
            public Iterator<Metric> iterator() {
                return sessionFactory.getCurrentSession()
                        .createQuery("from Metric as metric")
                        .iterate();
            }
        };
    }
}

это может тогда использоваться в коде как это:

class DaoUser {
    private MetricDao dao;
    for (Metric existing : dao.loadAll()) {
        // do stuff here...
    }
}

который позволяет мне использовать компактный цикл for, сохраняя при этом добавочное использование памяти.

Этот подход является «ленивым» - работа не выполняется, когда запрашивается Iterable, а только позже, когда содержимое повторяется, - и вам нужно знать о последствиях этого. В примере с DAO это означает итерацию результатов в транзакции базы данных.

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


хороший ответ, но вы returning a fresh Iterator on each callдолжны сопровождаться, почему, например, чтобы предотвратить проблему параллелизма ...
Anirudha

7

Невероятно, но еще никто не дал этот ответ. Вот как вы можете «легко» выполнять итерации Iteratorс помощью нового Iterator.forEachRemaining()метода Java 8 :

Iterator<String> it = ...
it.forEachRemaining(System.out::println);

Конечно, есть «более простое» решение, которое работает с циклом foreach напрямую, заключая Iteratorв Iterableлямбду:

for (String s : (Iterable<String>) () -> it)
    System.out.println(s);

5

Iteratorэто интерфейс, который позволяет вам перебирать что-то Это реализация перемещения какой-то коллекции.

Iterable это функциональный интерфейс, который обозначает, что что-то содержит доступный итератор.

В Java8 это делает жизнь довольно легкой ... Если у вас есть, Iteratorно нужна, Iterableвы можете просто сделать:

Iterator<T> someIterator;
Iterable<T> = ()->someIterator;

Это также работает в цикле for:

for (T item : ()->someIterator){
    //doSomething with item
}

2

Я согласен с принятым ответом, но хочу добавить собственное объяснение.

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

  • Iterable представляет коллекцию, которую можно обойти, он может вернуть столько итераторов, сколько вы хотите, каждый из которых представляет свое собственное состояние обхода, один итератор может указывать на первый элемент, а другой может указывать на 3-й элемент.

Было бы хорошо, если бы цикл Java for принимал и Iterator, и Iterable.


2

Чтобы избежать зависимости от java.util пакета

Согласно исходному JSR, расширенному циклу for для языка программирования Java ™ , предложенные интерфейсы:

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator
    (предлагается установить на него java.util.Iterator, но, видимо, этого не произошло)

… Были разработаны для использования java.langпространства имен пакета, а неjava.util .

Процитирую JSR:

Эти новые интерфейсы служат для предотвращения зависимости языка от java.util, которая в противном случае могла бы возникнуть.


Кстати, старый java.util.Iterableполучил новый forEachметод в Java 8+ для использования с лямбда-синтаксисом (передача a Consumer).

Вот пример. ListИнтерфейс расширяет Iterableинтерфейс, как и любой список несет forEachметод.

List
.of ( "dog" , "cat" , "bird" )
.forEach ( ( String animal ) -> System.out.println ( "animal = " + animal ) );

2

Я также вижу, что многие делают это:

public Iterator iterator() {
    return this;
}

Но это не делает это правильно! Этот метод не будет тем, что вы хотите!

Предполагается, что метод iterator()возвращает новый итератор, начиная с нуля. Так что нужно сделать что-то вроде этого:

public class IterableIterator implements Iterator, Iterable {

  //Constructor
  IterableIterator(SomeType initdata)
  {
    this.initdata = iter.initdata;
  }
  // methods of Iterable

  public Iterator iterator() {
    return new IterableIterator(this.intidata);
  }

  // methods of Iterator

  public boolean hasNext() {
    // ...
  }

  public Object next() {
    // ...
  }

  public void remove() {
    // ...
  }
}

Вопрос в том, будет ли какой-нибудь способ сделать абстрактный класс, выполняющий это? Чтобы получить IterableIterator, нужно всего лишь реализовать два метода next () и hasNext ()


1

Если вы пришли сюда в поисках обходного пути, вы можете использовать IteratorIterable . (доступно для Java 1.6 и выше)

Пример использования (реверсирование вектора).

import java.util.Vector;
import org.apache.commons.collections4.iterators.IteratorIterable;
import org.apache.commons.collections4.iterators.ReverseListIterator;
public class Test {
    public static void main(String ... args) {
        Vector<String> vs = new Vector<String>();
        vs.add("one");
        vs.add("two");
        for ( String s: vs ) {
            System.out.println(s);
        }
        Iterable<String> is
            = new IteratorIterable(new ReverseListIterator(vs));
        for ( String s: is ) {
            System.out.println(s);
        }
    }
}

печать

one
two
two
one

0

Для простоты Iterator и Iterable - это две разные концепции, Iterable - это просто сокращение от «Я могу вернуть Iterator». Я думаю, что ваш код должен быть:

for(Object o : someContainer) {
}

с экземпляром someContainer SomeContainer extends Iterable<Object>



0

В соответствующей заметке вы можете найти адаптер IteratorIterable в Apache Commons Collections4 полезным. Просто создайте экземпляр из итератора, и вы получите соответствующий итератор.

https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/iterators/IteratorIterable.html

ID: org.apache.commons: commons-collection4: 4.0


0

Итераторы с состоянием, имеют «следующий» элемент и становятся «исчерпанными» после итерации. Чтобы увидеть, в чем проблема, запустите следующий код, сколько чисел напечатано?

Iterator<Integer> iterator = Arrays.asList(1,2,3).iterator();
Iterable<Integer> myIterable = ()->iterator;
for(Integer i : myIterable) System.out.print(i);
System.out.println();
for(Integer i : myIterable) System.out.print(i);

-1

Вы можете попробовать следующий пример:

List ispresent=new ArrayList();
Iterator iterator=ispresent.iterator();
while(iterator.hasNext())
{
    System.out.println(iterator.next());
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.