Java'nın Yineleyicisi neden Yinelenebilir Değil?


178

IteratorArayüz neden genişlemiyor Iterable?

iterator()Yöntem basitçe geri dönebilirler this.

Java tasarımcılarının bilerek mi yoksa sadece bir gözetimi mi?

Bunun gibi yineleyicilerle her biri için bir döngü kullanabilmek uygun olacaktır:

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

burada listSomeObjects()bir yineleyici döndürür.


1
Tamam - puan almanı görüyorum. ]:] tüm cevaplar için Thank U: Bir semantiğini biraz kırdı bile hala kullanışlı olurdu
Łukasz Bownik

Bu sorunun uzun zaman önce sorulduğunu anlıyorum, ama - herhangi bir Yineleyici mi, yoksa sadece bazı Koleksiyonlarla ilgili bir Yineleyici mi demek istediniz?
einpoklum

Yanıtlar:


67

Çünkü yineleyici genellikle bir koleksiyondaki tek bir örneği gösterir. Tekrarlanabilir bir şey, bir nesnenin öğeleri üzerinde geçiş yapmak için bir yineleyici elde edebileceği anlamına gelir ve tek bir örnek üzerinde yinelemeye gerek yoktur, bu da yineleyicinin temsil ettiği şeydir.


25
+1: Bir koleksiyon yinelenebilir. Yineleyici yinelenemez, çünkü bu bir koleksiyon değildir.
S.Lott

50
Cevabı kabul ederken, zihniyete katılıp katılmadığımı bilmiyorum. Yinelenebilir arabirim tek bir yöntem sunar: Yineleyici <?> İterator (); Her durumda, her biri için bir yineleyici belirleyebilmeliyim. Ben satın almıyorum.
Chris K

25
@ S.Lott orada güzel dairesel akıl yürütme.
yihtserns

16
@ S.Lott Son deneme: Koleksiyon ∈ Tekrarlanabilir. Yineleyici ≠ Koleksiyon ∴ Yineleyici ∉ Yinelenebilir
yihtserns

27
@ S.Lott: Koleksiyon bu tartışma ile tamamen alakasız. Koleksiyon, tekrarlanabilir birçok olası uygulamadan sadece biridir. Bir şeyin Koleksiyon olmadığı gerçeğinin, Yinelenebilir olup olmadığı üzerinde hiçbir etkisi yoktur.
ColinD

218

Yineleyici durum gösterir. Fikir şu ki, Iterable.iterator()iki kez ararsanız, bağımsız yineleyiciler elde edersiniz - çoğu tekrar için yine de. Senaryonuzda durum böyle olmazdı.

Örneğin, genellikle şunu yazabilirim:

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

Bu, koleksiyonu iki kez yazdırmalıdır - ancak şemanızla ikinci döngü her zaman anında sona erer.


17
@Chris: Bir uygulama aynı yineleyiciyi iki kez döndürürse, yineleyici sözleşmesini nasıl yerine getirebilir? Sonucu çağırır iteratorve kullanırsanız, koleksiyon üzerinde yineleme yapmalıdır - bu, aynı nesne koleksiyon üzerinde zaten yinelenmişse yapmaz. Aynı yineleyicinin iki kez döndürüldüğü herhangi bir doğru uygulamayı (boş bir koleksiyon dışında) verebilir misiniz ?
Jon Skeet

7
Bu harika bir yanıt Jon, burada gerçekten sorunun en önemli noktası var. Yazık bu kabul edilen cevap değil! Tekrarlanabilir sözleşme kesin olarak tanımlanmıştır, ancak yukarıdakiler, Iterator uygulamasının Yinelenebilir (foreach için) uygulamasının arabirimin ruhunu bozmasının nedenlerinin büyük bir açıklamasıdır.
joelittlejohn

3
@JonSkeet bir Yineleyici <T> durum bilgisi açıkken, Yinelenebilir <T> sözleşmesi% 99 vakada senaryo olsa bile bağımsız yineleyiciler almak için iki kez kullanılabilecek bir şey söylemez. Tekrarlanabilir <T>, bir nesnenin her nesnenin hedefi olmasını sağlar. <T> Tekrarlanabilir <T> olmama konusunda mutsuz olanlarınız için böyle bir Tekrarlayıcı <T> yapmakta özgürsünüz. Sözleşmeyi bir parça kırmazdı. Bununla birlikte - Yineleyicinin kendisi Yinelenebilir olmamalıdır, çünkü bu dairesel olarak bağımlı olacaktır ve bu da icky tasarım için zemin hazırlar.
Centril

2
@Centril: Doğru. Genellikleiterable iki kez aramanın size bağımsız yineleyiciler vereceğini belirtmek için düzenlediniz .
Jon Skeet

2
Bu onun kalbine ulaşıyor. Kendini sıfırlayarak .iterator () uygulayan bir Yinelenebilir Yinelemeyi uygulamak neredeyse mümkün olurdu, ancak bu tasarım bazı durumlarda yine de kırılabilir, örneğin, yinelenebilir bir yöntem alan ve tüm olası çiftler üzerinde döngüye girerse Her bir döngüyü iç içe geçirerek öğelerin toplanması.
Theodore Murdock

60

Benim 0.02 $ için, Iterator Yinelenebilir uygulamak gerektiğini tamamen katılıyorum, ama ben döngü için gelişmiş ya da kabul gerektiğini düşünüyorum. Bence tüm "yineleyicileri yinelenebilir hale getir" argümanı, dilde bir kusurun etrafında bir çözüm olarak ortaya çıkıyor.

Geliştirilmiş döngü için tüm neden, "koleksiyonlar ve diziler üzerinde yineleme yaparken yineleyicilerin ve indeks değişkenlerinin anlamsızlığını ve hata eğilimini ortadan kaldırmasıdır" [ 1 ].

Collection<Item> items...

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

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

Öyleyse neden aynı argüman yineleyiciler için geçerli değil?

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

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

Her iki durumda da, hasNext () ve next () çağrıları kaldırılmıştır ve iç döngüde yineleyiciye başvuru yoktur. Evet, Iterables'ın birden çok yineleyici oluşturmak için yeniden kullanılabileceğini anlıyorum, ancak hepsi for döngüsünün dışında gerçekleşiyor: döngü içinde yineleyici tarafından döndürülen öğeler üzerinde bir seferde sadece bir ileri ilerleme var.

Ayrıca, buna izin vermek, başka bir yerde işaret edildiği gibi Tekrarlanamayan Yineleyicilere benzeyen Numaralandırmalar için for döngüsünü kullanmayı kolaylaştıracaktır.

Bu nedenle, Iterator'ı Tekrarlanabilir uygulama yapmayın, ancak for döngüsünü de kabul edin.

Alkış,


6
Katılıyorum. Teorik olarak, bir yineleyici alırken, bir kısmını kullanırken ve sonra bir foreach'a (foreach'in "her" sözleşmesini kırarak) sokarken karışıklık olabilir, ancak bu özelliğe sahip olmamak için yeterince iyi bir neden olduğunu düşünmüyorum.
Bart van Heukelom

Dizileri yinelenebilir toplama yapmak yerine dizileri kabul etmek için döngüyü güncelledik / geliştirdik. Bu kararı rasyonelleştirebilir misiniz?
Val

Bu cevabı iptal ettim ve bu bir hataydı. Yineleyici durumsaldır, for(item : iter) {...}sözdizimiyle yineleyici üzerinden yinelemeyi teşvik ederseniz , aynı yineleyici iki kez yinelendiğinde bir hataya neden olur. Düşünün Iteratorgeçirilir iterateOveryöntemin yerine Iterablede bu örnekte .
Ilya Silvestrov

2
for (String x : strings) {...}Veya while (strings.hasNext()) {...}stilini kullanıp kullanmamanız önemli değildir : ikinci kez iki kez bir yineleyici ile döngü yapmaya çalışırsanız sonuç vermez, bu yüzden bunu kendi içinde gelişmiş sözdizimine izin vermek için bir argüman olarak görmüyorum. Jon'un cevabı farklı, çünkü orada Iteratorbir Iterable'i sarmanın sorunlara neden olacağını gösteriyor, çünkü bu durumda onu istediğiniz kadar tekrar kullanabileceğinizi umuyorsunuz.
Barney

17

Başkaları tarafından işaret edildiği gibi Iteratorve Iterableiki farklı şeydir.

Ayrıca, Iteratordöngüler için uygulamalardan önce geliştirilmiştir.

Statik yöntem içe aktarmalarında kullanıldığında şöyle görünen basit bir bağdaştırıcı yöntemiyle bu sınırlamanın üstesinden gelmek de önemlidir:

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

Örnek uygulama:

  /**
   * 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 adapte olarak bir Iteratorbir için Iterablebasit alır:

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

bir fonksiyonda bir sınıf ilan ettiniz; bir şey mi kaçırıyorum? bunun yasa dışı olduğunu düşündüm.
activedecay

Java'daki bir blokta bir sınıf tanımlanabilir. Buna yerel sınıf
Colin D Bennett

3
İçin teşekkürlerfor (String s : (Iterable<String>) () -> iterator)
AlikElzin-kilaka

8

Diğerlerinin söylediği gibi, tekrarlanabilir bir şekilde birden çok kez çağrılabilir ve her çağrıda yeni bir Yineleyici döndürülebilir; bir yineleyici sadece bir kez kullanılır. Yani birbiriyle ilişkilidir, ancak farklı amaçlara hizmet ederler. Ancak, sinir bozucu bir şekilde, "kompakt" yöntemi yalnızca bir yinelenebilir ile çalışır.

Aşağıda açıklayacağım şey, her iki dünyanın da en iyisine sahip olmanın bir yoludur - temeldeki veri dizisi bir kerelik olsa bile Yinelenebilir (güzel sözdizimi için) döndürme.

İşin püf noktası, gerçekten çalışmayı tetikleyen Yinelenebilir'in anonim bir uygulamasını döndürmektir. Bu nedenle, bir defalık bir dizi oluşturan işi yapmak ve ardından bir Yineleyici döndürmek yerine, her erişildiğinde işi yeniden yineleyen bir Yinelenebilir döndürürsünüz. Bu savurgan görünebilir, ancak çoğu zaman yinelenebilir bir şekilde yine de bir kez çağırırsınız ve birden çok kez çağırsanız bile, hala makul anlambilime sahiptir (bir Yineleyiciyi "Yinelenebilir" gibi görünen basit bir sargının aksine, t iki kez kullanılırsa başarısız olur).

Örneğin, bir veritabanından bir dizi nesne sağlayan bir DAO'm var ve buna bir yineleyici aracılığıyla erişim sağlamak istiyorum (örneğin, gerekli değilse, bellekteki tüm nesneleri oluşturmaktan kaçınmak için). Şimdi sadece bir yineleyici döndürebilir, ancak bu bir döngüde döndürülen değeri çirkin kullanarak yapar. Bunun yerine her şeyi yinelenebilir bir anon'a sararım:

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

bu daha sonra şu şekilde kodda kullanılabilir:

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

bu da artımlı bellek kullanımını korurken kompakt for loop'u kullanmama izin veriyor.

Bu yaklaşım "tembel" dir - iş tekrarlanabilir istendiğinde değil, ancak daha sonra içerik yinelendiğinde yapılır - ve bunun sonuçlarının farkında olmanız gerekir. Veritabanı işlemindeki sonuçlar üzerinden yineleme anlamına gelen bir DAO örneğinde.

Yani çeşitli uyarılar var, ancak bu birçok durumda hala yararlı bir deyim olabilir.


güzel ans ama eşzamanlılık sorunu önlemek için neden yani returning a fresh Iterator on each calleşlik etmelidir ...
Anirudha

7

İnanılmaz bir şekilde, henüz kimse bu cevabı vermedi. IteratorYeni Java 8 Iterator.forEachRemaining()yöntemini kullanarak nasıl "kolayca" yineleyebileceğiniz aşağıda açıklanmıştır :

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

Tabii ki, doğrudan foreach döngüsü ile çalışır, sarmanın bir "basit" çözümü vardır Iteratorbir in Iterablelambda:

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

5

Iteratorbir şeyi yinelemenizi sağlayan bir arayüzdür. Bir çeşit koleksiyondan geçmenin bir uygulamasıdır.

Iterable bir şeyin erişilebilir bir yineleyici içerdiğini gösteren işlevsel bir arabirimdir.

Java8'de bu, hayatı oldukça kolaylaştırır ... Bir Iteratorihtiyacınız varsa, ancak Iterableşunları yapabilirsiniz:

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

Bu, for-loop'ta da çalışır:

for (T item : ()->someIterator){
    //doSomething with item
}

2

Kabul edilen cevaba katılıyorum, ancak kendi açıklamamı eklemek istiyorum.

  • Yineleyici geçişin durumunu temsil eder, örneğin, geçerli öğeyi bir yineleyiciden alabilir ve bir sonrakine geçebilirsiniz.

  • Yinelenebilir, geçilebilen bir koleksiyonu temsil eder, istediğiniz kadar yineleyici döndürebilir, her biri kendi geçiş durumunu temsil eder, bir yineleyici ilk öğeyi işaret ederken, diğeri 3. öğeyi işaret ediyor olabilir.

Döngü için Java hem Yineleyici hem de Yinelenebilir kabul ederse iyi olurdu.


2

java.utilPakete bağımlılığı önlemek için

Orijinal JSR'ye göre , Java ™ Programlama Dili için geliştirilmiş bir döngü için önerilen arayüzler:

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator
    (üzerine uyarlanması önerildi java.util.Iterator, ancak görünüşe göre bu hiç olmadı)

java.langPaket ad alanını kullanmak için tasarlandı java.util.

JSR'yi alıntılamak için:

Bu yeni arabirimler, dilin java.util dosyasına bağımlılığını önlemeye yarar.


Bu arada, eski Java 8+ 'da lambda sözdizimi ile kullanım için java.util.Iterableyeni bir forEachyöntem kazandı (a geçerek Consumer).

İşte bir örnek. ListArayüzü uzanan Iterableherhangi bir liste, bir taşıyıcı görevi arabirimi forEachyöntem.

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

2

Ayrıca bunu yapan birçok kişi görüyorum:

public Iterator iterator() {
    return this;
}

Ama bu doğru değil! Bu yöntem istediğiniz gibi olmaz!

Yöntemin iterator()sıfırdan başlayarak yeni bir yineleyici döndürmesi gerekiyor. Yani böyle bir şey yapmak gerekiyor:

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

Soru şudur: Bunu yapan soyut bir sınıf yapmanın herhangi bir yolu var mı? Bir IterableIterator almak için sadece next () ve hasNext () iki yöntemi uygulamak gerekir


1

Buraya bir geçici çözüm bulmak için geldiyseniz, Iterator'ı kullanabilirsiniz. . (Java 1.6 ve üstü için kullanılabilir)

Örnek kullanım (bir Vektörün ters çevrilmesi).

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

baskılar

one
two
two
one

0

Basitlik uğruna, Yineleyici ve Yinelenebilir iki farklı kavramdır, Yinelenebilir basitçe "Bir Yineleyiciyi iade edebilirim" için bir kısayoldur. Kodunuzun olması gerektiğini düşünüyorum:

for(Object o : someContainer) {
}

someContainer örneği ile SomeContainer extends Iterable<Object>




0

Yineleyiciler durumsaldır, bir "sonraki" öğeye sahiptirler ve yinelendiklerinde "tükenmiş" olurlar. Sorunun nerede olduğunu görmek için aşağıdaki kodu çalıştırın, kaç sayı yazdırılır?

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

Aşağıdaki örneği deneyebilirsiniz:

List ispresent=new ArrayList();
Iterator iterator=ispresent.iterator();
while(iterator.hasNext())
{
    System.out.println(iterator.next());
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.