Yinelenebilir <T> neden stream () ve parallelStream () yöntemleri sağlamaz?


241

IterableArayüzün neden stream()ve parallelStream()yöntemleri sağlamadığını merak ediyorum . Aşağıdaki sınıfı düşünün:

public class Hand implements Iterable<Card> {
    private final List<Card> list = new ArrayList<>();
    private final int capacity;

    //...

    @Override
    public Iterator<Card> iterator() {
        return list.iterator();
    }
}

Bir Trading Kart Oyunu oynarken elinizde kartlar olabileceği için bir El uygulamasıdır .

Temel olarak a'yı sarar List<Card>, maksimum kapasite sağlar ve diğer bazı yararlı özellikler sunar. Doğrudan bir olarak uygulamak daha iyidir List<Card>.

Şimdi, rahatlık için uygulamanın güzel olacağını düşündüm Iterable<Card>, böylece üzerinde döngü yapmak istiyorsanız gelişmiş for-loop'ları kullanabilirsiniz. ( HandSınıfım da bir get(int index)yöntem sunuyor, bu yüzden Iterable<Card>bence haklı.)

IterableArayüz aşağıdaki (sol dışarı javadoc) içerir:

public interface Iterable<T> {
    Iterator<T> iterator();

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}

Şimdi şununla bir akış elde edebilirsiniz:

Stream<Hand> stream = StreamSupport.stream(hand.spliterator(), false);

Gerçek soru üzerine:

  • Neden Iterable<T>uygulayan varsayılan yöntemler sağlamaz stream()ve parallelStream()bunu imkansız veya istenmeyen hale getirecek hiçbir şey görmüyorum?

Yine de bulduğum bir soru şudur: Akış <T> neden Yinelenebilir <T> uygulamıyor?
Hangi garip bir şekilde bunu yapmak için başka bir yol düşündürüyor.


1
Sanırım bu Lambda Posta Listesi için iyi bir soru .
Edwin Dalorzo

Bir akış üzerinden yineleme yapmak neden garip? Başka break;bir yineleme nasıl olabilir ? (Tamam, Stream.findFirst()bir çözüm olabilir, ancak bu tüm ihtiyaçları karşılamayabilir ...)
glglgl

Ayrıca pratik çözümler için Java 8 JDK kullanarak Yinelenebilir Akışa Dönüştürme konusuna bakın .
Vadzim

Yanıtlar:


298

Bu bir ihmal değildi; Haziran 2013'te EG listesinde ayrıntılı tartışma vardı.

Uzman Grubun kesin tartışması bu konuya dayanmaktadır .

Her ne stream()kadar mantıklı görünen "açık" (başlangıçta Uzman Grup için bile) gibi görünse Iterablede Iterable, bu kadar genel olan gerçek bir sorun haline geldi, çünkü açık imza:

Stream<T> stream()

her zaman isteyeceğiniz şey değildi. Örneğin, Iterable<Integer>akış yöntemini döndürmeyi tercih eden bazı şeyler IntStream. Ancak stream()yöntemi hiyerarşide bu kadar yükseğe koymak bunu imkansız hale getirecektir. Bunun yerine, bir yöntem sağlayarak bir ' Streamden kolay hale getirdik . Uygulanması in adildir:Iterablespliterator()stream()Collection

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

Herhangi bir müşteri istedikleri akışı aşağıdakilerden biriyle alabilir Iterable:

Stream s = StreamSupport.stream(iter.spliterator(), false);

Sonunda biz ekleyerek sonucuna stream()göre Iterablebir hata olur.


8
Her şeyden önce soruyu cevapladığınız için çok teşekkürler. Bir neden olsa hala merak ediyorum Iterable<Integer>(ben bahsediyorsun sanırım?) Bir dönüş isterim IntStream. O zaman tekrarlanabilir bir değil PrimitiveIterator.OfIntmi? Yoksa belki de başka bir kullanım anlamına mı geliyorsunuz?
skiwi

139
Yukarıdaki mantığın Iterable (sözde stream () olamaz çünkü birisi IntStream dönmek isteyebilir) uygulanan garip bulurken, aynı yöntemi Koleksiyon (aynı yöntemi eklemek için eşit miktarda düşünce verilmemiştir <Integer> Koleksiyonumun () akışının da IntStream'e dönmesini isteyebilirim.) Her ikisinde de mevcut olsun ya da olmasın, insanlar muhtemelen hayatlarına yeni kavuşurlardı, ancak birinde mevcut ve yokluğunda Diğeri, oldukça göze batan bir ihmal olur ...
Trejkaz

6
Brian McCutchon: Bu benim için daha anlamlı. İnsanların tartışmaktan bıktıkları ve onu güvenli oynamaya karar verdikleri anlaşılıyor.
Jonathan Locke

44
Bu mantıklı olsa Stream.of(Iterable)da, en azından API belgelerini okuyarak yöntemi makul bir şekilde keşfedilebilir hale getirecek alternatif bir statik olmamasının bir nedeni var mı? hatta baktım en StreamSupport"çoğunlukla kütüphane yazarlar için" dir "düşük seviyeli işlemleri" sağlamak gibi belgelerde anlatılan, hangi.
Jules

9
Jules ile tamamen aynı fikirdeyim. StreamSupport.stream (iter.spliterator (), false) yerine bir statik yöntem Stream.of (Yinelenebilir iter) veya Stream.of (Iterator iter) eklenmelidir;
user_3380739

23

Lambda posta listelerinin birkaçında araştırma yaptım ve birkaç ilginç tartışma bulduğumu düşünüyorum.

Şimdiye kadar tatmin edici bir açıklama bulamadım. Bütün bunları okuduktan sonra sadece bir ihmal olduğu sonucuna vardım. Ancak burada, API'nın tasarımı sırasında yıllar boyunca birkaç kez tartışıldığını görebilirsiniz.

Lambda Libs Spec Uzmanları

Bu konuda Lambda Libs Spec Experts posta listesinde bir tartışma buldum :

Tekrarlanabilir / Iterator.stream () altında Sam Pullara şöyle dedi:

Brian ile limit / subream işlevselliğinin [1] nasıl uygulanabileceğini görmek için çalışıyordum ve Iterator'a dönüşümün bu konuda doğru yol olduğunu önerdi. Bu çözümü düşünmüştüm ama bir yineleyici alıp bir akıma dönüştürmek için açık bir yol bulamadım. Orada olduğu ortaya çıkıyor, sadece yineleyiciyi bir ayırıcıya dönüştürmeniz ve ardından ayırıcıyı bir akıma dönüştürmeniz gerekiyor. Bu da beni, bunların Tekrarlanabilir / Yineleyiciden birini doğrudan mı yoksa her ikisini birden mi kapatıp açmamamız gerektiğine tekrar bakmamı sağlıyor.

Benim önerim, en azından Iterator'a sahip olmaktır, böylece iki dünya arasında temiz bir şekilde hareket edebilirsiniz ve bunu yapmak yerine kolayca keşfedilebilir olacaktır:

Streams.stream (Spliterators.spliteratorUnknownSize (yineleyici, Spliterator.ORDERED))

Ve sonra Brian Goetz cevap verdi :

Bence Sam'in amacı size bir Yineleyici veren ama mutlaka kendi bölücünüzü yazmanıza izin vermeyen çok sayıda kütüphane dersi olmasıydı. Tek yapmanız gereken çağrı akışı (spliteratorUnknownSize (iterator)). Sam, bunu sizin için yapmak üzere Iterator.stream () yöntemini tanımlamamızı öneriyor.

Stream () ve spliterator () yöntemlerini kütüphane yazarları / ileri düzey kullanıcılar olarak tutmak istiyorum.

Ve sonra

"Bir Spliterator yazmanın bir Iterator yazmaktan daha kolay olduğu göz önüne alındığında, sadece bir Iterator yerine bir Spliterator yazmayı tercih ederim (Iterator 90'lar gibi :)"

Yine de noktayı kaçırıyorsun. Zaten size bir Yineleyici veren milyonlarca ders var . Ve birçoğu ayırıcıya hazır değil.

Önceki Tartışmalar Lambda Posta Listesinde

Aradığınız cevap bu olmayabilir, ancak Project Lambda posta listesinde bu kısaca tartışıldı. Belki de bu konu hakkında daha geniş bir tartışmayı teşvik etmeye yardımcı olur.

Brian Goetz'un Tekrarlanabilir Akımlar altındaki sözleriyle :

Geri adım atmak...

Akış oluşturmanın birçok yolu vardır. Öğeleri nasıl açıklayacağınız hakkında daha fazla bilgiye sahip olduğunuzda, akış kütüphanesinin size verebileceği daha fazla işlevsellik ve performans. En azından çoğu bilgi için, bunlar:

yineleyici

Yineleyici + boyutu

Spliterator

Boyutunu bilen ayırıcı

Boyutunu bilen ve tüm alt bölümlerin boyutlarını bildiğini bilen ayırıcı.

(Bazıları, Q'nun (element başına çalışma) önemsiz olduğu durumlarda, aptal bir yineleyiciden bile paralellik elde edebileceğimizi şaşırtabilir.)

Iterable bir stream () yöntemine sahip olsaydı, bir Iterator'ı bir Spliterator ile boyut bilgisi olmadan sarardı. Ama, tekrarlanabilir olduğundan çoğu şey yapmak boyut bilgiler var. Bu da eksik akışlara hizmet ettiğimiz anlamına geliyor. O kadar iyi değil.

Burada Stephen tarafından özetlenen API Koleksiyonunun bir dezavantajı, Koleksiyon yerine Yinelenebilir'i kabul etmenin, bir şeyleri "küçük bir borudan" zorlamanız ve dolayısıyla yararlı olabileceği zaman boyut bilgilerini atmanızdır. Yapmanız gereken tek şey her şey için iyidir, ancak daha fazlasını yapmak istiyorsanız, istediğiniz tüm bilgileri koruyabilmeniz daha iyidir.

Yinelenebilir tarafından sağlanan varsayılan gerçekten berbat bir şey olurdu - Iterables'ın büyük çoğunluğu bu bilgiyi bilmesine rağmen boyutu atacaktı.

Çelişki?

Her ne kadar, tartışma, Uzman Grubun başlangıçta yineleyicilere dayanan Akışların ilk tasarımında yaptığı değişikliklere dayanıyor gibi görünüyor.

Yine de, Koleksiyon gibi bir arayüzde, akış yönteminin şu şekilde tanımlandığını fark etmek ilginçtir:

default Stream<E> stream() {
   return StreamSupport.stream(spliterator(), false);
}

Hangi yinelenebilir arabirimde kullanılan aynı kod olabilir.

Bu yüzden, bu cevabın muhtemelen tatmin edici değil, ama tartışma için hala ilginç olduğunu söyledim.

Yeniden Düzenleme Kanıtı

Posta listesindeki analize devam edersek, splitIterator yönteminin orijinal olarak Koleksiyon arabiriminde olduğu ve 2013'ün bir noktasında bunu Yinelenebilir'e taşıdıkları görülüyor.

SplitIterator öğesini Koleksiyondan Yinelenebilir'e doğru çekin .

Netice / Kuramları?

Daha sonra şansı, Iterable'deki yöntemin eksikliğinin sadece bir ihmal olmasıdır, çünkü splitIterator'ı Collection'dan Iterable'e kadar taşıdıklarında akış yöntemini de taşımış olmaları gerekir.

Başka nedenler varsa bunlar belli değildir. Başka birinin başka teorileri var mı?


Cevabınız için teşekkür ederim, ancak oradaki gerekçeye katılmıyorum. Eğer geçersiz O anda spliterator()içinde Iterable, sonra tüm sorunları giderilmiştir ve trivially uygulayabileceği stream()ve parallelStream()..
skiwi

@skiwi Bu yüzden bunun muhtemelen cevap olmadığını söyledim. Sadece tartışmaya katkıda bulunmaya çalışıyorum, çünkü uzman grubun neden kararlar aldığını bilmek zor. Sanırım tek yapabileceğimiz, posta listesinde bazı adli tıp yapmaya çalışmak ve herhangi bir sebeple gelip gelemeyeceğimizi görmek.
Edwin Dalorzo

1
@skiwi Diğer posta listelerini inceledim ve tartışma için daha fazla kanıt buldum ve belki de bazı teşhisleri teorikleştirmeye yardımcı olacak bazı fikirler buldum.
Edwin Dalorzo

Çabalarınız için teşekkürler, bu posta listelerini nasıl verimli bir şekilde böleceğinizi öğrenmeliyim. Bir forum ya da bir şey gibi ... modern bir şekilde görselleştirilebilirlerse, yardımcı olur, çünkü içinde tırnak işaretleri olan düz metin e-postaları okumak tam olarak verimli değildir.
skiwi

6

Kullanabileceğiniz boyutu biliyorsanız java.util.Collectionhangi stream()yöntemi sağlar:

public class Hand extends AbstractCollection<Card> {
   private final List<Card> list = new ArrayList<>();
   private final int capacity;

   //...

   @Override
   public Iterator<Card> iterator() {
       return list.iterator();
   }

   @Override
   public int size() {
      return list.size();
   }
}

Ve sonra:

new Hand().stream().map(...)

Aynı sorunla karşı karşıya ve benim o şaşırdı Iterableuygulaması çok kolay bir şekilde genişletilebilir olabilir AbstractCollectionbasitçe ekleyerek uygulanmasısize() yöntemi (neyse ki koleksiyonun boyutu vardı :-)

Ayrıca geçersiz kılmayı da düşünmelisiniz Spliterator<E> spliterator().

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.