Perché Iterable non fornisce i metodi stream () e parallelStream ()?

Mi chiedo perché l’interfaccia Iterable non fornisce i metodi stream() e parallelStream() . Considera la seguente class:

 public class Hand implements Iterable { private final List list = new ArrayList(); private final int capacity; //... @Override public Iterator iterator() { return list.iterator(); } } 

È un’implementazione di una mano in quanto puoi avere carte in mano mentre giochi a un gioco di carte collezionabili.

Essenzialmente racchiude un List , garantisce una capacità massima e offre alcune altre utili funzioni. È meglio implementarlo direttamente come List .

Ora, per convenienza, ho pensato che sarebbe stato bello implementare Iterable , in modo tale che tu possa utilizzare i for-loop ottimizzati se desideri Iterable looping. (La class My Hand fornisce anche un metodo get(int index) , quindi l’ Iterable è giustificato secondo me.)

L’interfaccia Iterable fornisce quanto segue (javadoc lasciato fuori):

 public interface Iterable { Iterator iterator(); default void forEach(Consumer action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } default Spliterator spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); } } 

Ora puoi ottenere un stream con:

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

Quindi sulla vera domanda:

  • Perché Iterable non fornisce metodi predefiniti che implementano stream() e parallelStream() , non vedo nulla che possa rendere questo imansible o indesiderato?

Una domanda correlata che ho trovato è la seguente: Perché Stream non implementa Iterable ?
Il che è abbastanza strano suggerire di farlo al contrario.

Questa non era un’omissione; ci fu una discussione dettagliata sulla lista EG nel giugno del 2013.

La discussione definitiva sul gruppo di esperti è radicata in questa discussione .

Mentre sembrava “ovvio” (anche per il gruppo di esperti, inizialmente) che stream() sembrava avere un senso su Iterable , il fatto che Iterable fosse così generale divenne un problema, perché la firma ovvia:

 Stream stream() 

non era sempre quello che volevi. Alcune cose che erano Iterable preferiscono che il loro metodo di stream restituisca un IntStream , ad esempio. Ma mettere il metodo stream() così in alto nella gerarchia lo renderebbe imansible. Così, invece, abbiamo reso molto semplice creare un Stream da un Iterable , fornendo un metodo spliterator() . L’implementazione di stream() in Collection è solo:

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

Qualsiasi cliente può ottenere il stream che desidera da un Iterable con:

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

Alla fine abbiamo concluso che l’aggiunta di stream() a Iterable sarebbe un errore.

Ho fatto un’indagine in molte delle mailing list lambda del progetto e penso di aver trovato alcune discussioni interessanti.

Non ho trovato una spiegazione soddisfacente finora. Dopo aver letto tutto ciò, ho concluso che era solo un’omissione. Ma qui puoi vedere che è stato discusso più volte nel corso degli anni durante la progettazione dell’API.

Lambda Libs Spec Experts

Ho trovato una discussione su questo nella mailing list degli esperti di Lambda Libs Spec :

Sotto Iterable / Iterator.stream () Sam Pullara ha detto:

Stavo lavorando con Brian per vedere come la funzionalità limit / substream [1] potrebbe essere implementata e ha suggerito che la conversione in Iterator fosse il modo giusto per farlo. Avevo pensato a questa soluzione ma non ho trovato alcun modo ovvio per prendere un iteratore e trasformarlo in un stream. Si scopre che è lì, devi solo prima convertire l’iteratore in uno splitterator e quindi convertire lo spliterator in un stream. Quindi questo mi porta a rivisitare se dovremmo avere questi appendere uno dei Iterable / Iterator direttamente o entrambi.

Il mio suggerimento è di averlo almeno su Iterator in modo da poterti spostare in modo pulito tra i due mondi e sarebbe anche facilmente individuabile piuttosto che dover fare:

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

E poi Brian Goetz ha risposto :

Penso che il punto di Sam fosse che ci sono un sacco di classi di librerie che ti danno un Iterator ma non ti permettono di scrivere necessariamente il tuo splitterator. Quindi tutto ciò che puoi fare è chiamare lo stream (spliteratorUnknownSize (iterator)). Sam sta suggerendo di definire Iterator.stream () per farlo per te.

Vorrei mantenere i metodi stream () e spliterator () come per gli scrittori di librerie / utenti avanzati.

E più tardi

“Dato che scrivere uno Spliterator è più facile che scrivere un Iterator, preferirei semplicemente scrivere uno Spliterator invece di un Iterator (Iterator è così 90s :)”

Ti manca il punto, però. Ci sono milioni di lezioni là fuori che ti consegnano già un Iterator. E molti di loro non sono pronti per lo splitteratore.

Discussioni precedenti in Lambda Mailing List

Questa potrebbe non essere la risposta che stai cercando, ma nella mailing list di Project Lambda questo è stato brevemente discusso. Forse questo aiuta a promuovere una discussione più ampia sull’argomento.

Nelle parole di Brian Goetz sotto Streams from Iterable :

Arretrare…

Esistono molti modi per creare un stream. Più informazioni hai su come descrivere gli elementi, più funzionalità e prestazioni la libreria di flussi può darti. In ordine di almeno la maggior parte delle informazioni, sono:

Iterator

Iteratore + taglia

Spliterator

Spliterator che conosce le sue dimensioni

Spliterator che conosce le sue dimensioni e sa anche che tutte le sub-split conoscono le loro dimensioni.

(Alcuni potrebbero essere sorpresi di scoprire che possiamo estrarre il parallelismo anche da un iteratore stupido nei casi in cui Q (lavoro per elemento) non è banale.)

Se Iterable avesse un metodo stream (), sarebbe sufficiente racchiudere un Iterator con uno Spliterator, senza informazioni sulle dimensioni. Ma la maggior parte delle cose che sono Iterable hanno informazioni sulle dimensioni. Il che significa che stiamo offrendo flussi insufficienti. Non è così bello.

Uno svantaggio della pratica dell’API descritta da Stephen qui, di accettare Iterable invece di Collection, è che stai forzando le cose attraverso una “small pipe” e quindi scartando le informazioni sulle dimensioni quando potrebbe essere utile. Va bene se tutto quello che stai facendo è forOgnalo, ma se vuoi fare di più, è meglio se puoi conservare tutte le informazioni che vuoi.

Il valore predefinito fornito da Iterable sarebbe in effetti uno schifoso: eliminerebbe le dimensioni anche se la maggior parte degli Iterables conosce tali informazioni.

Contraddizione?

Sebbene, sembra che la discussione si basi sui cambiamenti che il Gruppo di esperti ha fatto per la progettazione iniziale degli Stream, inizialmente basata su iteratori.

Anche così, è interessante notare che in un’interfaccia come Collection, il metodo stream è definito come:

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

Quale potrebbe essere esattamente lo stesso codice utilizzato nell’interfaccia Iterable.

Quindi, questo è il motivo per cui ho detto che questa risposta non è probabilmente soddisfacente, ma comunque interessante per la discussione.

Prove di refactoring

Continuando con l’analisi nella mailing list, sembra che il metodo splitIterator fosse originariamente nell’interfaccia Collection, e ad un certo punto nel 2013 lo hanno spostato fino a Iterable.

Estrai split Iterator da Collection a Iterable .

Conclusione / Theories?

Quindi è probabile che la mancanza del metodo in Iterable sia solo un’omissione, poiché sembra che avrebbero dovuto spostare anche il metodo stream quando hanno spostato splitIterator da Collection a Iterable.

Se ci sono altri motivi, questi non sono evidenti. Qualcun altro ha altre teorie?

Se conosci la dimensione, puoi utilizzare java.util.Collection che fornisce il metodo stream() :

 public class Hand extends AbstractCollection { private final List list = new ArrayList<>(); private final int capacity; //... @Override public Iterator iterator() { return list.iterator(); } @Override public int size() { return list.size(); } } 

E poi:

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

Ho affrontato lo stesso problema ed ero sorpreso che la mia implementazione Iterable potesse essere facilmente estesa ad un’implementazione di AbstractCollection semplicemente aggiungendo il metodo size() (fortunatamente avevo le dimensioni della collezione 🙂

Si dovrebbe anche considerare di ignorare Spliterator spliterator() .