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


241

Mi chiedo perché l' Iterableinterfaccia non fornisca i metodi stream()e parallelStream(). Considera la seguente classe:

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

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

In sostanza avvolge a List<Card>, garantisce una capacità massima e offre alcune altre utili funzionalità. È meglio implementarlo direttamente come a List<Card>.

Ora, per comodità, ho pensato che sarebbe stato bello implementarlo Iterable<Card>, in modo tale che tu possa usare i for-loop avanzati se vuoi passare sopra. (La mia Handclasse fornisce anche un get(int index)metodo, quindi Iterable<Card>è giustificato secondo me.)

L' Iterableinterfaccia fornisce quanto segue (escluso javadoc):

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

Ora puoi ottenere uno stream con:

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

Quindi sulla vera domanda:

  • Perché Iterable<T>non fornisce metodi predefiniti da implementare stream()e parallelStream(), non vedo nulla che lo renderebbe impossibile o indesiderato?

Una domanda correlata che ho trovato è la seguente: Perché Stream <T> non implementa Iterable <T>?
Il che è stranamente suggerito che lo faccia un po 'al contrario.


1
Immagino che questa sia una buona domanda per la mailing list Lambda .
Edwin Dalorzo,

Perché è strano voler iterare su uno stream? In quale altro modo potresti eventualmente break;eseguire un'iterazione? (Ok, Stream.findFirst()potrebbe essere una soluzione, ma potrebbe non soddisfare tutte le esigenze ...)
glglgl,

Vedi anche Converti Iterable in Stream utilizzando Java 8 JDK per soluzioni pratiche.
Vadzim,

Risposte:


298

Questa non era un'omissione; c'è stata una discussione dettagliata sull'elenco EG nel giugno del 2013.

La discussione definitiva del gruppo di esperti è radicata su questo thread .

Mentre sembrava "ovvio" (persino al gruppo di esperti, inizialmente) che stream()sembrava avere senso Iterable, il fatto che Iterablefosse così generale divenne un problema, perché la firma ovvia:

Stream<T> stream()

non era sempre quello che avresti voluto. Alcune cose che Iterable<Integer>avrebbero preferito avere il loro metodo stream restituiscono un IntStream, per esempio. Ma mettere il stream()metodo così in alto nella gerarchia lo renderebbe impossibile. Quindi, invece, abbiamo reso davvero facile fare un Streamda un Iterable, fornendo un spliterator()metodo. L'implementazione di stream()in Collectionè solo:

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

Qualsiasi client può ottenere il flusso desiderato da un Iterablecon:

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

Alla fine abbiamo concluso che aggiungere stream()a Iterablesarebbe stato un errore.


8
Vedo, innanzitutto, grazie mille per aver risposto alla domanda. Sono ancora curioso di Iterable<Integer>sapere perché un (penso che tu stia parlando?) Vorrebbe restituire un IntStream. L'iterabile non sarebbe piuttosto un PrimitiveIterator.OfInt? O forse vuoi dire un altro caso d'uso?
Skiwi,

139
Trovo strano che la logica di cui sopra sia stata presumibilmente applicata a Iterable (non posso avere stream () perché qualcuno potrebbe desiderare che restituisca IntStream) mentre una pari quantità di pensiero non è stata data all'aggiunta dello stesso metodo esatto a Collection ( Potrei desiderare che il flusso <Integer> della mia raccolta () restituisca anche IntStream.) Sia che fosse presente su entrambi o assente su entrambi, le persone probabilmente sarebbero appena andate avanti con le loro vite, ma perché è presente su uno e assente su l'altro diventa un'omissione lampante ...
Trejkaz,

6
Brian McCutchon: Questo ha più senso per me. Sembra che la gente si sia appena stancata di litigare e abbia deciso di giocare sul sicuro.
Jonathan Locke,

44
Anche se questo ha senso, c'è un motivo per cui non esiste una statica alternativa Stream.of(Iterable), che almeno renderebbe il metodo ragionevolmente rilevabile leggendo la documentazione dell'API - come qualcuno che non ha mai davvero lavorato con gli interni di Streams che non avrei mai anche guardato a StreamSupport, che è descritto nella documentazione a fornire "operazioni di basso livello", che sono "in gran parte per gli scrittori libreria".
Jules il

9
Sono totalmente d'accordo con Jules. è necessario aggiungere un metodo statico Stream.of (Iteratable iter) o Stream.of (Iterator iter), anziché StreamSupport.stream (iter.spliterator (), false);
user_3380739

23

Ho fatto un'indagine in diverse mailing list del progetto lambda e penso di aver trovato alcune discussioni interessanti.

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

Esperti di specifiche di Lambda Libs

Ho trovato una discussione al riguardo nella mailing list degli esperti di Lambda Libs :

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

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

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

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

E poi Brian Goetz ha risposto :

Penso che il punto di Sam fosse che ci sono molte classi di biblioteche che ti danno un Iteratore ma non ti lasciano necessariamente scrivere il tuo proprio divisore. Quindi tutto ciò che puoi fare è chiamare il flusso (spliteratorUnknownSize (iteratore)). 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 uno Iterator, preferirei semplicemente scrivere uno Spliterator invece di uno Iterator (Iterator è così anni '90 :)"

Ti manca il punto, però. Ci sono miliardi di classi là fuori che ti danno già un Iteratore. E molti di loro non sono pronti per lo splitterator.

Discussioni precedenti in Lambda Mailing List

Questa potrebbe non essere la risposta che stai cercando, ma nella mailing list di Project Lambda questa è stata brevemente discussa. Forse questo aiuta a favorire una discussione più ampia sull'argomento.

Nelle parole di Brian Goetz sotto Streams from Iterable :

Arretrare...

Esistono molti modi per creare uno stream. Più informazioni hai su come descrivere gli elementi, maggiore è la funzionalità e le prestazioni che la libreria di flussi può darti. Per almeno la maggior parte delle informazioni, sono:

Iterator

Iteratore + dimensione

Spliterator

Spliterator che conosce le sue dimensioni

Spliterator che conosce le sue dimensioni e inoltre sa che tutte le suddivisioni secondarie conoscono le loro dimensioni.

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

Se Iterable avesse un metodo stream (), avvolgerebbe semplicemente un Iterator con un Spliterator, senza informazioni sulle dimensioni. Ma, la maggior parte delle cose che sono Iterable fare avere informazioni sulle dimensioni. Ciò significa che stiamo offrendo flussi carenti. Non va molto bene.

Un aspetto negativo della pratica API delineata da Stephen qui, di accettare Iterable invece di Collection, è che stai forzando le cose attraverso una "piccola pipe" e quindi scartando le informazioni sulle dimensioni quando potrebbero essere utili. Va bene se tutto ciò che stai facendo è per ogni cosa, ma se vuoi fare di più, è meglio se puoi conservare tutte le informazioni che desideri.

L'impostazione predefinita fornita da Iterable sarebbe davvero scadente - eliminerebbe le dimensioni anche se la stragrande maggioranza degli Iterable conosce tali informazioni.

Contraddizione?

Tuttavia, sembra che la discussione si basi sui cambiamenti che il gruppo di esperti ha apportato alla progettazione iniziale di Stream, inizialmente basata su iteratori.

Tuttavia, è interessante notare che in un'interfaccia come Collection, il metodo stream è definito come:

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

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

Quindi, ecco perché ho detto che questa risposta non è probabilmente soddisfacente, ma comunque interessante per la discussione.

Prova 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 su Iterable.

Tirare splitIterator in su 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?


Apprezzo la tua risposta, ma non sono d'accordo con il ragionamento lì. Nel momento in cui si ignora la spliterator()della Iterable, allora tutti i problemi ci sono fissi, e si può banalmente implementare stream()e parallelStream()..
skiwi

@skiwi Ecco perché ho detto che questa probabilmente non è la risposta. Sto solo cercando di aggiungere alla discussione, perché è difficile sapere perché il gruppo di esperti abbia preso le decisioni che ha preso. Immagino che tutto ciò che possiamo fare è provare a fare delle analisi forensi nella mailing list e vedere se possiamo trovare qualche motivo.
Edwin Dalorzo,

1
@skiwi Ho esaminato altre mailing list e ho trovato ulteriori prove per la discussione e forse alcune idee che aiutano a teorizzare qualche diagnosi.
Edwin Dalorzo,

Grazie per i tuoi sforzi, dovrei davvero imparare come suddividere in modo efficiente quelle mailing list. Sarebbe di aiuto se potessero essere visualizzati in qualche modo ... moderno, come un forum o qualcosa del genere, perché leggere e-mail di testo semplice con virgolette non è esattamente efficiente.
Skiwi,

6

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

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

E poi:

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

Ho affrontato lo stesso problema e sono rimasto sorpreso dal fatto che la mia Iterableimplementazione potesse essere facilmente estesa a AbstractCollectionun'implementazione semplicemente aggiungendo il size()metodo (fortunatamente avevo le dimensioni della collezione :-)

Dovresti anche considerare di eseguire l'override Spliterator<E> spliterator().

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.