Mengapa Java Iterator bukan Iterable?

178

Mengapa Iteratorantarmuka tidak diperluas Iterable?

The iterator()Metode hanya bisa kembali this.

Apakah itu disengaja atau hanya pengawasan desainer Jawa?

Akan lebih mudah untuk menggunakan loop untuk-setiap dengan iterator seperti ini:

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

di mana listSomeObjects()mengembalikan iterator.

Łukasz Bownik
sumber
1
OK - saya mengerti maksud Anda tetapi. masih akan tetap nyaman bahkan jika itu sedikit memecahkan semantik:] Terima kasih atas semua jawaban:]
Łukasz Bownik
Saya menyadari pertanyaan ini sudah lama ditanyakan, tetapi - apakah Anda maksud Iterator, atau hanya Iterator yang berkaitan dengan beberapa Koleksi?
einpoklum

Jawaban:

67

Karena iterator biasanya menunjuk ke satu instance dalam koleksi. Iterable menyiratkan bahwa seseorang dapat memperoleh iterator dari objek untuk dilintasi elemen-elemennya - dan tidak perlu untuk mengulangi sebuah instance tunggal, yang merupakan apa yang diwakili oleh iterator.

PaulJWilliams
sumber
25
+1: Sebuah koleksi dapat dihapus. Sebuah iterator tidak dapat diubah karena itu bukan koleksi.
S.Lott
50
Sementara saya setuju dengan jawabannya, saya tidak tahu apakah saya setuju dengan mentalitasnya. Antarmuka Iterable menyajikan metode tunggal: Iterator <?> Iterator (); Dalam kasus apa pun, saya harus dapat menentukan iterator untuk masing-masing. Saya tidak membelinya.
Chris K
25
@ S.Banyak alasan melingkar bagus di sana.
yihtserns
16
@ S.Lott Upaya terakhir: Koleksi ∈ Iterable. Iterator ≠ Koleksi ∴ Iterator ∉ Iterable
yihtserns
27
@ S.Lott: Koleksi sama sekali tidak relevan dengan diskusi ini. Koleksi hanyalah salah satu dari banyak kemungkinan implementasi Iterable. Fakta bahwa sesuatu bukanlah suatu Koleksi tidak ada hubungannya dengan apakah itu Iterable.
ColinD
218

Sebuah iterator stateful. Idenya adalah bahwa jika Anda menelepon Iterable.iterator()dua kali, Anda akan mendapatkan iterator independen - untuk sebagian besar iterables. Itu jelas tidak akan menjadi kasus dalam skenario Anda.

Sebagai contoh, saya biasanya dapat menulis:

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

Itu harus mencetak koleksi dua kali - tetapi dengan skema Anda, loop kedua akan selalu berakhir secara instan.

Jon Skeet
sumber
17
@ Chris: Jika suatu implementasi mengembalikan iterator yang sama dua kali, bagaimana bisa memenuhi kontrak Iterator? Jika Anda memanggil iteratordan menggunakan hasilnya, ia harus mengulangi koleksi - yang tidak akan dilakukan jika objek yang sama telah diulangi pada koleksi. Anda dapat memberikan setiap pelaksanaan yang benar (selain untuk koleksi kosong) di mana iterator yang sama dikembalikan dua kali?
Jon Skeet
7
Ini adalah respons yang bagus, Jon, Anda benar-benar mendapatkan inti masalahnya di sini. Malu ini bukan jawaban yang diterima! Kontrak untuk Iterable secara tegas didefinisikan, tetapi di atas adalah penjelasan yang bagus tentang alasan mengapa memungkinkan Iterator untuk menerapkan Iterable (foreach) akan mematahkan semangat antarmuka.
joelittlejohn
3
@JonSkeet sementara Iterator <T> stateful, kontrak Iterable <T> tidak mengatakan apa-apa tentang bisa digunakan dua kali untuk mendapatkan iterator independen, bahkan itu adalah skenario dalam 99% kasus. Semua yang bisa dikatakan <T> mengatakan bahwa itu memungkinkan objek untuk menjadi target foreach. Bagi Anda yang tidak puas dengan Iterator <T> tidak menjadi Iterable <T>, Anda bebas untuk membuat Iterator <T> tersebut. Itu tidak akan merusak kontrak sedikit pun. Namun - Iterator itu sendiri tidak boleh Iterable karena itu akan membuatnya tergantung secara melingkar dan menjadi landasan bagi desain yang menjijikkan.
Centril
2
@Centril: Benar. Telah diedit untuk menunjukkan bahwa biasanya menelepon iterabledua kali akan memberi Anda iterator independen.
Jon Skeet
2
Ini sampai ke jantungnya. Hampir mungkin untuk mengimplementasikan Iterable Iterator yang mengimplementasikan .iterator () dengan mengatur ulang sendiri, tetapi desain ini masih akan pecah dalam beberapa keadaan, misalnya, jika diteruskan ke metode yang membutuhkan Iterable dan lilitkan semua pasangan yang memungkinkan. elemen dengan bersarang untuk-setiap loop.
Theodore Murdock
60

Untuk $ 0,02 saya, saya sepenuhnya setuju bahwa Iterator tidak boleh menerapkan Iterable, tetapi saya pikir peningkatan untuk loop harus menerima baik. Saya pikir seluruh argumen "buat iterator menjadi iterable" muncul sebagai upaya memperbaiki kecacatan dalam bahasa.

Seluruh alasan untuk pengenalan loop yang disempurnakan adalah bahwa hal itu "menghilangkan kerepotan dan kesalahan-kesalahan dari iterator dan variabel indeks ketika iterasi pada koleksi dan array" [ 1 ].

Collection<Item> items...

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

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

Lalu mengapa argumen yang sama ini tidak berlaku untuk iterator?

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

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

Dalam kedua kasus, panggilan ke hasNext () dan next () telah dihapus, dan tidak ada referensi ke iterator di loop dalam. Ya, saya mengerti bahwa Iterables dapat digunakan kembali untuk membuat beberapa iterator, tetapi itu semua terjadi di luar for for loop: di dalam loop hanya ada progres maju satu item pada waktu bersamaan dengan item yang dikembalikan oleh iterator.

Juga, membiarkan ini juga akan membuatnya mudah untuk menggunakan loop for Enumerations, yang, seperti telah ditunjukkan di tempat lain, analog dengan Iterator bukan Iterables.

Jadi jangan membuat Iterator mengimplementasikan Iterable, tetapi perbarui loop for untuk menerima keduanya.

Bersulang,

Barney
sumber
6
Saya setuju. Secara teoritis mungkin ada kebingungan ketika mendapatkan iterator, menggunakan bagian dari itu, dan kemudian meletakkannya di muka (melanggar kontrak "masing-masing" dari foreach), tapi saya tidak berpikir itu alasan yang cukup baik untuk tidak memiliki fitur ini.
Bart van Heukelom
Kami telah memperbarui / meningkatkan loop untuk menerima array alih-alih membuat koleksi array iterable. Bisakah Anda merasionalisasi keputusan ini?
Val
Saya membatalkan balasan ini, dan itu adalah kesalahan. Iterator adalah stateful, jika Anda mempromosikan iterasi lebih dari iterator dengan for(item : iter) {...}sintaks, maka iterator akan menimbulkan kesalahan ketika iterator yang sama diulang dua kali. Bayangkan yang Iteratordilewatkan ke iterateOvermetode bukan Iterabledalam contoh ini .
Ilya Silvestrov
2
Tidak masalah apakah Anda menggunakan gaya for (String x : strings) {...}atau while (strings.hasNext()) {...}: jika Anda mencoba untuk mengulangi iterator dua kali kedua kali tidak akan menghasilkan hasil, jadi saya tidak melihat itu sendiri sebagai argumen yang menentang memperbolehkan sintaks yang ditingkatkan. Jawaban Jon berbeda, karena di sana dia menunjukkan bagaimana membungkus sebuah Iteratordalam Iterableakan menyebabkan masalah, seperti dalam kasus itu Anda akan mengharapkan untuk dapat menggunakannya kembali sebanyak yang Anda suka.
Barney
17

Seperti yang ditunjukkan oleh orang lain, Iteratordan Iterableada dua hal yang berbeda.

Juga, Iteratorimplementasi lebih dulu ditingkatkan untuk loop.

Juga sepele untuk mengatasi batasan ini dengan metode adaptor sederhana yang terlihat seperti ini ketika digunakan dengan impor metode statis:

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

Implementasi sampel:

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

Di Java 8 mengadaptasi Iteratorsuatu Iterablemenjadi lebih sederhana:

for (String s : (Iterable<String>) () -> iterator) {
McDowell
sumber
Anda mendeklarasikan kelas dalam suatu fungsi; apakah saya melewatkan sesuatu? Saya pikir ini ilegal.
Diaktifkanecay
Kelas dapat didefinisikan dalam sebuah blok di Jawa. Ini disebut kelas lokal
Colin D Bennett
3
Terima kasih untukfor (String s : (Iterable<String>) () -> iterator)
AlikElzin-kilaka
8

Seperti yang orang lain katakan, Iterable dapat dipanggil beberapa kali, mengembalikan Iterator baru pada setiap panggilan; Iterator hanya digunakan sekali. Jadi mereka saling berhubungan, tetapi melayani tujuan yang berbeda. Namun, frustrasi, metode "kompak untuk" hanya bekerja dengan iterable.

Apa yang akan saya jelaskan di bawah ini adalah salah satu cara untuk mendapatkan yang terbaik dari kedua dunia - mengembalikan sebuah Iterable (untuk sintaksis yang lebih baik) bahkan ketika urutan data yang mendasarinya adalah sekali saja.

Caranya adalah dengan mengembalikan implementasi anterable dari Iterable yang sebenarnya memicu pekerjaan. Jadi alih-alih melakukan pekerjaan yang menghasilkan urutan satu kali dan kemudian mengembalikan Iterator lebih dari itu, Anda mengembalikan Iterable yang, setiap kali diakses, mengulangi pekerjaan. Itu mungkin tampak sia-sia, tetapi sering kali Anda hanya akan memanggil Iterable sekali saja, dan bahkan jika Anda menyebutnya berkali-kali, itu masih memiliki semantik yang masuk akal (tidak seperti pembungkus sederhana yang membuat Iterator "terlihat seperti" yang Iterable, ini akan menang ' t gagal jika digunakan dua kali).

Misalnya, saya memiliki DAO yang menyediakan serangkaian objek dari database, dan saya ingin memberikan akses ke sana melalui iterator (mis. Untuk menghindari membuat semua objek dalam memori jika tidak diperlukan). Sekarang saya hanya bisa mengembalikan iterator, tetapi itu membuat menggunakan nilai yang dikembalikan dalam satu lingkaran jelek. Jadi alih-alih saya membungkus semuanya dalam 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();
            }
        };
    }
}

ini kemudian dapat digunakan dalam kode seperti ini:

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

yang memungkinkan saya menggunakan compact untuk loop sambil tetap menjaga penggunaan memori tambahan.

Pendekatan ini "malas" - pekerjaan tidak dilakukan ketika Iterable diminta, tetapi hanya nanti ketika isinya diulangi - dan Anda perlu menyadari konsekuensi dari itu. Dalam contoh dengan DAO itu berarti mengulangi hasil dalam transaksi basis data.

Jadi ada berbagai peringatan, tetapi ini masih bisa menjadi ungkapan yang berguna dalam banyak kasus.

andrew cooke
sumber
ans bagus tapi Anda returning a fresh Iterator on each callharus disertai dengan mengapa yaitu untuk mencegah masalah konkurensi ...
Anirudha
7

Hebatnya, belum ada orang lain yang memberikan jawaban ini. Inilah cara Anda dapat "dengan mudah" mengulanginya Iteratordengan menggunakan metode Java 8 baru Iterator.forEachRemaining():

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

Tentu saja, ada solusi "sederhana" yang bekerja dengan loop foreach secara langsung, dengan membungkusnya Iteratordalam Iterablelambda:

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

Iteratoradalah antarmuka yang memungkinkan Anda untuk beralih pada sesuatu. Ini adalah implementasi bergerak melalui koleksi sejenis.

Iterable adalah antarmuka fungsional yang menunjukkan bahwa sesuatu mengandung iterator yang dapat diakses.

Di Java8, ini membuat hidup sangat mudah ... Jika Anda memiliki Iteratortetapi perlu, IterableAnda dapat melakukannya:

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

Ini juga berfungsi di for-loop:

for (T item : ()->someIterator){
    //doSomething with item
}
Steve K.
sumber
2

Saya setuju dengan jawaban yang diterima, tetapi ingin menambahkan penjelasan saya sendiri.

  • Iterator mewakili keadaan traverse, misalnya, Anda bisa mendapatkan elemen saat ini dari iterator dan bergerak maju ke yang berikutnya.

  • Iterable mewakili koleksi yang dapat dilintasi, ia dapat mengembalikan sebanyak mungkin iterator yang Anda inginkan, masing-masing mewakili keadaan traversenya sendiri, satu iterator mungkin menunjuk ke elemen pertama, sementara yang lain mungkin menunjuk ke elemen ke-3.

Akan lebih baik jika Java for loop menerima Iterator dan Iterable.

Dagang
sumber
2

Untuk menghindari ketergantungan pada java.utilpaket

Menurut JSR asli, Lingkaran yang disempurnakan untuk Java ™ Programming Language , antarmuka yang diusulkan:

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator
    (diusulkan untuk dipasang ke java.util.Iterator, tetapi tampaknya ini tidak pernah terjadi)

... dirancang untuk menggunakan java.langnamespace paket daripada java.util.

Mengutip JSR:

Antarmuka baru ini berfungsi untuk mencegah ketergantungan bahasa pada java.util yang jika tidak akan menghasilkan.


By the way, yang lama java.util.Iterablemendapatkan forEachmetode baru di Java 8+, untuk digunakan dengan sintaks lambda (lewat a Consumer).

Berikut ini sebuah contoh. The Listantarmuka meluas Iterableantarmuka, sebagai daftar setiap membawa forEachmetode.

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

Saya juga melihat banyak yang melakukan ini:

public Iterator iterator() {
    return this;
}

Tapi itu tidak benar! Metode ini tidak akan seperti yang Anda inginkan!

Metode iterator()ini seharusnya mengembalikan iterator baru mulai dari awal. Jadi orang perlu melakukan sesuatu seperti ini:

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

Pertanyaannya adalah: akankah ada cara untuk membuat kelas abstrak melakukan ini? Sehingga untuk mendapatkan IterableIterator kita hanya perlu mengimplementasikan dua metode next () dan hasNext ()

Martin Vatshelle
sumber
1

Jika Anda datang ke sini untuk mencari solusi, Anda dapat menggunakannya IteratorIterable . (tersedia untuk Java 1.6 ke atas)

Contoh penggunaan (membalikkan Vektor).

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

cetakan

one
two
two
one
serv-inc
sumber
0

Demi kesederhanaan, Iterator dan Iterable adalah dua konsep yang berbeda, Iterable hanyalah sebuah singkatan untuk "Aku bisa mengembalikan Iterator". Saya pikir kode Anda harus:

for(Object o : someContainer) {
}

dengan instanceContainer someof SomeContainer extends Iterable<Object>

dfa
sumber
0

Iterator bersifat stateful, mereka memiliki elemen "berikutnya" dan menjadi "habis" begitu iterasi. Untuk melihat di mana masalahnya, jalankan kode berikut, berapa angka yang dicetak?

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
sumber
-1

Anda dapat mencoba contoh berikut:

List ispresent=new ArrayList();
Iterator iterator=ispresent.iterator();
while(iterator.hasNext())
{
    System.out.println(iterator.next());
}
Sakthi King
sumber