Почему Java Iterator не является Iterable?

Asked
Viewd47185

178

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

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

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

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

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

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

  • Хорошо, но я понимаю вашу точку зрения. все равно было бы удобно, даже если бы это немного нарушило семантику:]

    Спасибо за все ответы:]

    Łukasz Bownik08 мая 2009, 10:53

16 ответов

68

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

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

    S.Lott08 мая 2009, 10:26
  • Хотя я согласен с ответом, я не знаю, согласен ли я с таким менталитетом. Интерфейс Iterable представляет собой единственный метод: Iterator > Iterator (); В любом случае я смогу указать итератор для каждого. Я не куплюсь на это.

    Chris K19 декабря 2009, 20:36
8

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

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

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

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

 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 это означает повторение результатов внутри транзакции базы данных.

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

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 к Iterable упрощается:

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

Для моих 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 для перечислений, которые, как было указано в другом месте, аналогичны Iterators, а не Iterables.

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

С уважением,

0

В стороне: Scala имеет метод toIterable () в Iterator. См. неявное или явное преобразование scala из итератора в итерабельный

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);
    }
}
 

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

  • Получите ли вы независимые итераторы, это полностью зависит от реализации. У вас нет возможности убедиться в этом.

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

    Jon Skeet19 декабря 2009, 20:57
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 ()

0

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

 for(Object o : someContainer) {
}
 

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