Dlaczego Iterator Java nie jest iterowalny?

178

Dlaczego Iteratorinterfejs się nie rozszerza Iterable?

iterator()Metoda może po prostu wrócić this.

Czy jest to celowe czy tylko nadzór nad projektantami Javy?

Byłoby wygodnie móc użyć pętli dla każdego z iteratorami w następujący sposób:

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

gdzie listSomeObjects()zwraca iterator.

Łukasz Bownik
źródło
1
OK - widzę twój punkt buut. byłoby nadal wygodne, nawet gdyby trochę złamało semantykę:] Dziękuję U za wszystkie odpowiedzi:]
Łukasz Bownik
Zdaję sobie sprawę, że to pytanie zostało zadane dawno temu, ale - czy masz na myśli jakiś iterator, czy tylko iterator odnoszący się do jakiejś kolekcji?
einpoklum

Odpowiedzi:

67

Ponieważ iterator ogólnie wskazuje na pojedyncze wystąpienie w kolekcji. Iterowalność oznacza, że ​​można uzyskać iterator z obiektu, aby przejść przez jego elementy - i nie ma potrzeby iteracji po pojedynczej instancji, co reprezentuje iterator.

PaulJWilliams
źródło
25
+1: kolekcja jest iterowalna. Iterator nie jest iterowalny, ponieważ nie jest kolekcją.
S.Lott,
50
Chociaż zgadzam się z odpowiedzią, nie wiem, czy zgadzam się z mentalnością. Interfejs Iterable przedstawia jedną metodę: Iterator <?> Iterator (); W każdym razie powinienem być w stanie określić iterator dla każdego z nich. Nie kupuję tego
Chris K,
25
@ S.Lott ładne okrągłe rozumowanie.
yihtserns
16
@ S.Lott Ostatnia próba: kolekcja ∈ Iterable. Iterator ≠ Kolekcja ∴ Iterator ∉ Iterable
yihtserns
27
@ S.Lott: Kolekcja jest całkowicie nieistotna dla tej dyskusji. Kolekcja to tylko jedna z wielu możliwych implementacji Iterable. Fakt, że coś nie jest kolekcją, nie ma wpływu na to, czy jest to iterowalna.
ColinD
218

Iterator jest stanowy. Chodzi o to, że jeśli zadzwonisz Iterable.iterator()dwukrotnie, otrzymasz niezależne iteratory - w każdym razie dla większości iteratorów. To oczywiście nie byłoby w twoim scenariuszu.

Na przykład zazwyczaj mogę pisać:

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

To powinno wydrukować kolekcję dwa razy - ale przy twoim schemacie druga pętla zawsze kończy się natychmiast.

Jon Skeet
źródło
17
@Chris: Jeśli implementacja zwraca ten sam iterator dwa razy, jak, u licha, mogłaby wypełnić kontrakt z Iteratorem? Jeśli zadzwonisz iteratori użyjesz wyniku, musi on iterować kolekcję - czego nie zrobi, jeśli ten sam obiekt już wykonał iterację w kolekcji. Można dać żadnej poprawnej realizacji (inne niż do pustej kolekcji), gdzie ten sam iterator jest zwracany dwukrotnie?
Jon Skeet,
7
To świetna odpowiedź Jon, naprawdę masz tutaj sedno problemu. Szkoda, że ​​to nie jest zaakceptowana odpowiedź! Kontrakt dla Iterable jest ściśle określony, ale powyższe jest doskonałym wyjaśnieniem powodów, dla których zezwolenie Iteratorowi na wdrożenie Iterable (dla foreach) złamałoby ducha interfejsu.
joelittlejohn
3
@JonSkeet podczas gdy Iterator <T> jest stanowy, umowa Iterable <T> nie mówi nic o możliwości dwukrotnego wykorzystania niezależnych iteratorów, nawet jeśli jest to scenariusz w 99% przypadków. Iterable <T> mówi tylko, że pozwala obiektowi stać się celem foreach. Dla tych z was, którzy są niezadowoleni z tego, że Iterator <T> nie jest Iterowalnym <T>, możecie zrobić taki Iterator <T>. Nie zerwie ani trochę umowy. Jednak sam iterator nie powinien być iterowalny, ponieważ sprawiłby, że byłby zależny od koła, co stanowi podstawę dla trudnego projektu.
Centril
2
@Centril: Racja. Edytowałem, aby wskazać, że zwykleiterable dwukrotne wywołanie daje niezależne iteratory.
Jon Skeet
2
To trafia do sedna. Byłoby prawie możliwe zaimplementowanie Iterowalnego Iteratora, który implementuje .iterator () poprzez zresetowanie samego siebie, ale ten projekt nadal by się zepsuł w niektórych okolicznościach, na przykład, gdyby został przekazany do metody, która przyjmuje Iterowalny i zapętla wszystkie możliwe pary elementów poprzez zagnieżdżanie dla każdej pętli.
Theodore Murdock,
60

Jeśli chodzi o moje 0,02 USD, całkowicie zgadzam się, że Iterator nie powinien implementować Iterable, ale myślę, że ulepszona pętla for powinna zaakceptować oba. Myślę, że cały argument „iteratory iterable” pojawia się jako obejście wady języka.

Głównym powodem wprowadzenia ulepszonej pętli for było to, że „eliminuje ona znużenie i podatność na błędy iteratorów i zmiennych indeksowych podczas iteracji po kolekcjach i tablicach” [ 1 ].

Collection<Item> items...

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

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

Dlaczego zatem ten sam argument nie dotyczy iteratorów?

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

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

W obu przypadkach wywołania funkcji hasNext () i next () zostały usunięte, aw wewnętrznej pętli nie ma odwołania do iteratora. Tak, rozumiem, że iteratory mogą być ponownie użyte do utworzenia wielu iteratorów, ale wszystko dzieje się poza pętlą for: wewnątrz pętli występuje tylko jeden postęp w przód o jeden element w stosunku do elementów zwracanych przez iterator.

Umożliwienie tego również ułatwiłoby korzystanie z pętli for dla wyliczeń, które, jak wskazano w innym miejscu, są analogiczne do iteratorów, a nie iteratorów.

Więc nie zmuszaj Iteratora do implementacji Iterable, ale zaktualizuj pętlę for, aby zaakceptować oba.

Twoje zdrowie,

Barney
źródło
6
Zgadzam się. Teoretycznie może pojawić się zamieszanie przy pobieraniu iteratora, używaniu jego części, a następnie umieszczaniu go w foreach (zerwanie „każdego” kontraktu foreach), ale nie sądzę, żeby to był wystarczający powód, aby nie mieć tej funkcji.
Bart van Heukelom,
Zaktualizowaliśmy / poprawiliśmy już pętlę, aby akceptować tablice zamiast tworzyć kolekcję iterowalną. Czy możesz zracjonalizować tę decyzję?
Val
Głosowałem za odpowiedzią i był to błąd. Iterator jest stanowy, jeśli promujesz iterację nad iteratorem za pomocą for(item : iter) {...}składni, spowoduje to błąd, gdy iterator zostanie powtórzony dwukrotnie. Wyobraź sobie, że Iteratorjest przekazywany do iterateOvermetody zamiast Iterablew tym przykładzie .
Ilya Silvestrov,
2
To naprawdę nie ma znaczenia, czy użyć for (String x : strings) {...}lub while (strings.hasNext()) {...}styl: jeśli spróbujesz pętli przez dwukrotność czasu iteratora drugi przyniesie żadnych rezultatów, więc nie widzę, że sam w sobie jako argument przeciwko pozwalając składnię wzmocniona. Jon odpowiedź jest inna, bo on pokazuje jak owijania Iteratorw sposób Iterablemogłoby spowodować problemy, ponieważ w takim przypadku można oczekiwać, aby móc używać go tyle razy, ile ci się podoba.
Barney,
17

Jak zauważyli inni, Iteratori Iterablesą dwie różne rzeczy.

Ponadto wcześniejsze Iteratorimplementacje zostały ulepszone dla pętli.

Przełamanie tego ograniczenia jest również trywialne za pomocą prostej metody adaptera, która wygląda tak, gdy jest używana z importem metody statycznej:

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

Przykładowa implementacja:

  /**
   * 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();
  }

W Javie 8 adaptacja Iteratordo na Iterablestaje się prostsza:

for (String s : (Iterable<String>) () -> iterator) {
McDowell
źródło
zadeklarowałeś klasę w funkcji; czy coś mi brakuje? myślałem, że to nielegalne.
activedecay
Klasę można zdefiniować w bloku w Javie. Nazywa się to klasą lokalną
Colin D Bennett
3
Dzięki zafor (String s : (Iterable<String>) () -> iterator)
AlikElzin-kilaka
8

Jak powiedzieli inni, Iterable można wywołać wiele razy, zwracając nowy Iterator przy każdym wywołaniu; iterator jest używany tylko raz. Są więc powiązane, ale służą innym celom. Frustrujące jest jednak to, że metoda „compact for” działa tylko z iteracją.

To, co opiszę poniżej, to jeden ze sposobów na uzyskanie najlepszego z obu światów - zwracanie Iterable (dla lepszej składni), nawet gdy leżąca u podstaw sekwencja danych jest jednorazowa.

Sztuka polega na zwróceniu anonimowej implementacji Iterable, która faktycznie uruchamia pracę. Zamiast więc wykonać pracę, która generuje jednorazową sekwencję, a następnie zwrócić iterator, zwraca Iterable, który za każdym razem, gdy jest uzyskiwany dostęp, przerywa pracę. Może się to wydawać marnotrawstwem, ale często i tak wywołujesz Iterable tylko raz, a nawet jeśli wywołasz to wiele razy, nadal ma rozsądną semantykę (w przeciwieństwie do prostego opakowania, które sprawia, że ​​Iterator „wygląda jak” Iterowalny, to wygrało ” nie powiedzie się, jeśli zostanie użyty dwukrotnie).

Powiedzmy na przykład, że mam DAO, która udostępnia serię obiektów z bazy danych, i chcę zapewnić do nich dostęp za pośrednictwem iteratora (np. Aby uniknąć tworzenia wszystkich obiektów w pamięci, jeśli nie są one potrzebne). Teraz mógłbym po prostu zwrócić iterator, ale to sprawia, że ​​używanie zwracanej wartości w pętli jest brzydkie. Zamiast tego zawijam wszystko w anon 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();
            }
        };
    }
}

można to następnie wykorzystać w kodzie:

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

co pozwala mi korzystać z kompaktowej pętli dla, jednocześnie zachowując przyrostowe użycie pamięci.

To podejście jest „leniwe” - praca nie jest wykonywana na żądanie Iterable, ale dopiero później, gdy zawartość jest iterowana - i musisz zdawać sobie sprawę z konsekwencji tego. W przykładzie z DAO oznacza to iterację wyników w ramach transakcji bazy danych.

Istnieją więc różne zastrzeżenia, ale w wielu przypadkach może to nadal być przydatnym idiomem.

Andrzej Cooke
źródło
fajny ans, ale returning a fresh Iterator on each callpowinieneś towarzyszyć dlaczego, tj. aby uniknąć problemu współbieżności ...
Anirudha
7

Niesamowicie, nikt jeszcze nie udzielił tej odpowiedzi. Oto, w jaki sposób możesz „łatwo” iterować Iteratorza pomocą nowej Iterator.forEachRemaining()metody Java 8 :

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

Oczywiście istnieje „prostsze” rozwiązanie, które działa bezpośrednio z pętlą foreach, owijając ją Iteratorw Iterablelambda:

for (String s : (Iterable<String>) () -> it)
    System.out.println(s);
Lukas Eder
źródło
5

Iteratorto interfejs, który pozwala na iterację nad czymś. Jest to implementacja przechodzenia przez jakąś kolekcję.

Iterable to funkcjonalny interfejs, który oznacza, że ​​coś zawiera dostępny iterator.

W Javie 8 sprawia to, że życie jest dość łatwe ... Jeśli masz, Iteratorale potrzebujesz Iterable, możesz po prostu zrobić:

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

Działa to również w pętli for:

for (T item : ()->someIterator){
    //doSomething with item
}
Steve K.
źródło
2

Zgadzam się z przyjętą odpowiedzią, ale chcę dodać własne wyjaśnienie.

  • Iterator reprezentuje stan przejścia, np. Możesz pobrać bieżący element z iteratora i przejść do następnego.

  • Iterable reprezentuje kolekcję, którą można przemierzać, może zwrócić tyle iteratorów, ile chcesz, każda reprezentuje swój własny stan przejścia, jeden iterator może wskazywać na pierwszy element, a inny może wskazywać na element trzeci.

Byłoby miło, gdyby Java for loop akceptowała zarówno Iterator, jak i Iterable.

Dagang
źródło
2

Aby uniknąć zależności od java.utilpakietu

Zgodnie z oryginalnym JSR, Udoskonalona pętla for dla języka programowania Java ™ , proponowane interfejsy:

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator
    (proponowane do modernizacji java.util.Iterator, ale najwyraźniej tak się nigdy nie stało)

… Zostały zaprojektowane tak, aby używać java.langprzestrzeni nazw pakietu zamiast java.util.

Cytując JSR:

Te nowe interfejsy służą do zapobiegania zależności języka od pliku java.util, który w przeciwnym razie mógłby powstać.


Nawiasem mówiąc, stary java.util.Iterablezyskał nową forEachmetodę w Javie 8+, do użytku ze składnią lambda (przekazywanie a Consumer).

Oto przykład. ListInterfejs obejmuje Iterableinterfejs, jak każdy lista prowadzi forEachmetody.

List
.of ( "dog" , "cat" , "bird" )
.forEach ( ( String animal ) -> System.out.println ( "animal = " + animal ) );
Basil Bourque
źródło
2

Widzę też, że wielu to robi:

public Iterator iterator() {
    return this;
}

Ale to nie poprawia sytuacji! Ta metoda nie byłaby tym, czego chcesz!

Metoda iterator()ma zwrócić nowy iterator od zera. Trzeba więc zrobić coś takiego:

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() {
    // ...
  }
}

Pytanie brzmi: czy byłby jakiś sposób, aby klasa abstrakcyjna wykonywała to? Aby uzyskać IterableIterator wystarczy zaimplementować dwie metody next () i hasNext ()

Martin Vatshelle
źródło
1

Jeśli przyszedłeś tu w poszukiwaniu obejścia, możesz użyć IteratorIterable . (dostępne dla Java 1.6 i nowszych)

Przykładowe użycie (odwrócenie wektora).

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

odciski

one
two
two
one
serv-inc
źródło
0

Dla uproszczenia Iterator i Iterable to dwa odrębne pojęcia. Iterable to po prostu skrót dla „Mogę zwrócić Iterator”. Myślę, że twój kod powinien być:

for(Object o : someContainer) {
}

z instancją someContainer SomeContainer extends Iterable<Object>

dfa
źródło
0

Iteratory są stanowe, mają „następny” element i stają się „wyczerpane” po iteracji. Aby zobaczyć, gdzie jest problem, uruchom następujący kod, ile liczb jest drukowanych?

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);
Roland
źródło
-1

Możesz wypróbować następujący przykład:

List ispresent=new ArrayList();
Iterator iterator=ispresent.iterator();
while(iterator.hasNext())
{
    System.out.println(iterator.next());
}
Sakthi King
źródło