Почему итератор Java не является итерируемым? - PullRequest
166 голосов
/ 08 мая 2009

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

Метод iterator() может просто возвращать this.

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

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

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

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

Ответы [ 15 ]

209 голосов
/ 08 мая 2009

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

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

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

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

63 голосов
/ 08 мая 2009

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

55 голосов
/ 11 марта 2011

За мои 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, чтобы принять либо.

Приветствия

17 голосов
/ 19 декабря 2011

Как отмечают другие, 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 к Iterable упрощается:

for (String s : (Iterable<String>) () -> iterator) {
8 голосов
/ 06 февраля 2012

Как уже говорили другие, 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 for, сохраняя при этом добавочное использование памяти.

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

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

6 голосов
/ 21 декабря 2016

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

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

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

for (String s : (Iterable<String>) () -> it)
    System.out.println(s);
5 голосов
/ 04 ноября 2015

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

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

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

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

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

for (T item : ()->someIterator){
    //doSomething with item
}
2 голосов
/ 03 октября 2010

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

public Iterator iterator() {
    return this;
}

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

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

public class IterableIterator implements Iterator, Iterable {

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

  public Iterator iterator() {
    return new MyClass(this.somedata);
  }

  // methods of Iterator

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

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

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

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

1 голос
/ 06 сентября 2017

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

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

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

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

1 голос
/ 07 марта 2016

Если вы пришли сюда в поисках обходного пути, вы можете использовать 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
...