Чому ітератор Java не є ітерабельним?


178

Чому Iteratorінтерфейс не поширюєтьсяIterable ?

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

Це навмисно чи просто недогляд дизайнерів Java?

Було б зручно мати можливість використовувати для кожного циклу такі ітератори:

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

де listSomeObjects()повертає ітератор.


1
Гаразд - я бачу ваш сутовий бут. все-таки було б зручно, навіть якщо це трохи порушило семантику:] Дякую У за всі відповіді:]
Łukasz Bownik

Я усвідомлюю, що це питання було задано давно, але - ви маєте на увазі будь-який ітератор або просто ітератор, що стосується якоїсь колекції?
einpoklum

Відповіді:


67

Тому що ітератор, як правило, вказує на один екземпляр колекції. Ітерабельність передбачає, що можна отримати ітератор від об'єкта, щоб переходити його елементи - і немає необхідності перебирати один екземпляр, що є ітератором.


25
+1: Колекція доступна. Ітератор не ітерабельний, оскільки це не колекція.
С.Лотт

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

25
@ S.Lott приємні кругові міркування там.
yihtserns

16
@ S.Lott Остання спроба: Колекція ∈ Iterable. Iterator ≠ Колекція ∴ Iterator ∉ 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? Якщо ви зателефонуєте iteratorта використаєте результат, він має повторити колекцію - що не зробить, якщо той самий об’єкт уже повторив колекцію. Можете чи ви дати якісь - або правильної реалізації (за винятком порожній колекції) , де ж ітератора повертається в два рази?
Джон Скіт

7
Це чудова відповідь, Джон, ти справді маєш суть проблеми. Сором, це не прийнята відповідь! Контракт на Iterable суворо визначений, але вищезазначене є чудовим поясненням причин, через які дозволити Iterator реалізувати Iterable (for foreach) порушив би дух інтерфейсу.
joelittlejohn

3
@JonSkeet, хоча Iterator <T> є статтю, контракт Iterable <T> нічого не говорить про те, що можна використовувати два рази для отримання незалежних ітераторів, навіть так це сценарій у 99% випадків. Все Iterable <T> говорить про те, що це дозволяє об'єкту бути ціллю foreach. Для тих із вас, хто не задоволений тим, що Iterator <T> не є Iterable <T>, ви можете зробити такий Iterator <T>. Це не розірвало б контракт ні на один біт. Однак сам Iterator не повинен бути взаємодіючим, оскільки це зробить його кругозалежною, і це закладе грунт для прискіпливого дизайну.
Сентриль

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

2
Це дійсно доходить до серця цього. Практично можливо було б реалізувати Iterable Iterator, який реалізує .iterator () шляхом скидання себе, але ця конструкція все-таки порушиться в деяких обставинах, наприклад, якби вона була передана методу, який приймає Iterable і циклізує всі можливі пари елементів, вклавши для кожної петлі.
Теодор Мердок

60

За свої $ 0,02 я повністю погоджуюся з тим, що Iterator не повинен реалізовувати Iterable, але я думаю, що посилений для циклу повинен прийняти будь-який. Я думаю, що весь аргумент "зробити ітераторів ітерабельним" з'являється як обробка проблеми з дефектом мови.

Вся причина введення розширеного для циклу полягала в тому, що він "усуває дружинність і схильність до помилок ітераторів та індексів змінних при ітерації над колекціями та масивами" [ 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: всередині циклу лише один раз заздалегідь відбувається перехід вперед по елементах, повернутим ітератором.

Також, якщо це зробити, це також полегшило б використання циклу для перерахунків, які, як було зазначено в іншому місці, аналогічні Ітераторам, а не Ітерабельним.

Тому не змушуйте Iterator реалізовувати Iterable, але оновіть цикл for, щоб прийняти будь-який.

Ура,


6
Я згоден. Теоретично може виникнути плутанина, коли ви отримуєте ітератор, використовуючи його частину, а потім ставите його в передчуття (розриваючи "кожен" контракт 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 адаптування a Iteratorдо Iterableстає простішим:

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

ви оголосили клас у функції; я щось пропускаю? я думав, що це незаконно.
activedecay

Клас можна визначити в блоці на Java. Він називається місцевим класом
Колін Д Беннетт

3
Дякую заfor (String s : (Iterable<String>) () -> iterator)
AlikElzin-kilaka

8

Як говорили інші, Ітерабельний може викликатися кілька разів, повертаючи свіжий Ітератор під час кожного дзвінка; ітератор використовується лише один раз. Так вони пов’язані між собою, але служать різним цілям. Розчаровує, однак, "компактний для" метод працює лише з ітерабельним.

Що я опишу нижче, це один із способів отримати найкраще з обох світів - повернення Iterable (для кращого синтаксису), навіть коли основна послідовність даних є одноразовою.

Хитрість полягає в тому, щоб повернути анонімну реалізацію Iterable, яка фактично запускає роботу. Отже, замість того, щоб виконувати роботу, яка генерує одноразову послідовність, а потім повертаючи Ітератор над цим, ви повертаєте Iterable, який кожного разу, до якого звертаєтесь, повторює роботу. Це може здатися марним, але часто ви будете називати Ітерабельний лише один раз, і навіть якщо ви називаєте його кілька разів, він все ще має розумну семантику (на відміну від простої обгортки, яка робить Ітератор "схожим на" Ітерабельний, це переміг " t не вдасться, якщо використовується два рази).

Наприклад, скажіть, що у мене є 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...
    }
}

що дозволяє мені використовувати компакт для циклу, зберігаючи при цьому додаткове використання пам'яті.

Такий підхід є "ледачим" - робота робиться не тоді, коли запитується Ітерабельна, а лише пізніше, коли вміст переглядається, - і вам потрібно знати про наслідки цього. У прикладі з 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

Я згоден з прийнятою відповіддю, але хочу додати своє власне пояснення.

  • Ітератор представляє стан переходу, наприклад, ви можете отримати поточний елемент з ітератора і перейти до наступного.

  • Ітерабельний представляє колекцію, яку можна пройти, вона може повернути стільки ітераторів, скільки вам потрібно, кожен представляє власний стан переходу, один ітератор може вказувати на перший елемент, а інший може вказувати на 3-й елемент.

Було б добре, якщо Java for loop приймає і Iterator, і Iterable.


2

Щоб уникнути залежності від java.utilпакета

Відповідно до оригіналу JSR, запропоновані інтерфейси для розширеного циклу для мови програмування 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 - це просто скорочення для "я можу повернути ітератор". Я думаю, що ваш код повинен бути:

for(Object o : someContainer) {
}

з деяким экземпляром контейнера SomeContainer extends Iterable<Object>




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.