In Java, quali sono i vantaggi degli stream rispetto ai loop? [chiuso]


133

Mi è stato chiesto questo durante un'intervista e non sono convinto di aver dato la migliore risposta che avrei potuto avere. Ho detto che puoi fare una ricerca parallela e che i valori null sono stati gestiti da alcuni mezzi che non ricordo. Ora mi rendo conto che stavo pensando a Optionals. Cosa mi sto perdendo qui? Sostengono che sia un codice migliore o più conciso, ma non sono sicuro di essere d'accordo.


Considerando quanto brevemente sia stata data una risposta, sembra che dopo tutto non si sia trattato di una domanda troppo ampia.


Se pongono questa domanda durante le interviste, e chiaramente lo sono, quale scopo potrebbe abbatterlo oltre a rendere più difficile trovare una risposta? Voglio dire, cosa stai cercando? Potrei scomporre la domanda e avere una risposta a tutte le sotto-domande, ma poi creare una domanda principale con collegamenti a tutte le domande secondarie ... sembra piuttosto sciocca però. Mentre ci siamo, per favore fammi un esempio di una domanda meno ampia. Non conosco alcun modo per porre solo una parte di questa domanda e ottenere comunque una risposta significativa. Potrei fare esattamente la stessa domanda in modo diverso. Ad esempio, potrei chiedere "A quale scopo servono gli stream?" o "Quando dovrei usare un flusso anziché un ciclo for?" o "Perché preoccuparsi dei flussi invece che dei loop?" Queste sono tutte esattamente la stessa domanda però.

... o è considerato troppo ampio perché qualcuno ha dato una risposta multi-point davvero lunga? Francamente chiunque lo sappia potrebbe farlo praticamente con qualsiasi domanda. Se ti capita di essere uno degli autori della JVM, per esempio, potresti probabilmente parlare di loop tutto il giorno quando la maggior parte di noi non potrebbe.

"Modifica la domanda per limitarla a un problema specifico con dettagli sufficienti per identificare una risposta adeguata. Evita di porre più domande distinte contemporaneamente. Consulta la pagina Come chiedere aiuto per chiarire questa domanda."

Come indicato di seguito, è stata data una risposta adeguata che dimostra che ce n'è una e che è abbastanza facile da fornire.


7
Questo è basato sull'opinione pubblica. Personalmente, preferisco i flussi perché rende il codice più leggibile. Permette di scrivere quello che vuoi invece di come . Inoltre, è assolutamente tosto fare cose straordinarie con una linea.
Arnaud Denoyelle,

19
Anche se è una linea 30, una linea? Non amo le catene lunghe.
user447607,

1
Inoltre, tutto quello che cerco qui è la risposta appropriata per un'intervista. Questa è l'unica "opinione" che conta.
user447607,

1
Dal punto di vista educativo, questa domanda mi ha risparmiato un po 'di degrado anche in una futura intervista, @slim l'ha davvero inchiodato, ma a livello industriale, parla anche di come i linguaggi di programmazione Microsoft hanno costruito le loro carriere nel strappare il linguaggio Java, e infine Java si vendica strappando dalla Lambda Expression e dai flussi degli avversari, vediamo cosa farà java in futuro in Struct and Unions :)
ShayHaned

5
Si noti che i flussi sfruttano solo una frazione della potenza nella programmazione funzionale: - /
Thorbjørn Ravn Andersen

Risposte:


252

Interessante il fatto che la domanda dell'intervista ponga dei vantaggi, senza chiedere svantaggi, perché ci sono entrambi.

Gli stream sono uno stile più dichiarativo . O uno stile più espressivo . Potrebbe essere meglio dichiarare il tuo intento nel codice, piuttosto che descrivere come è fatto:

 return people
     .filter( p -> p.age() < 19)
     .collect(toList());

... dice chiaramente che stai filtrando gli elementi corrispondenti da un elenco, mentre:

 List<Person> filtered = new ArrayList<>();
 for(Person p : people) {
     if(p.age() < 19) {
         filtered.add(p);
     }
 }
 return filtered;

Dice "Sto facendo un giro". Lo scopo del loop è sepolto più in profondità nella logica.

Gli stream sono spesso più difficili . Lo stesso esempio mostra questo. Terser non è sempre migliore, ma se puoi essere conciso ed espressivo allo stesso tempo, tanto meglio.

Gli stream hanno una forte affinità con le funzioni . Java 8 introduce lambda e interfacce funzionali, che aprono un intero toybox di potenti tecniche. I flussi forniscono il modo più conveniente e naturale per applicare funzioni alle sequenze di oggetti.

I flussi incoraggiano una minore mutabilità . Questo è in qualche modo correlato all'aspetto della programmazione funzionale: il tipo di programmi che scrivi usando i flussi tende ad essere il tipo di programmi in cui non modifichi gli oggetti.

I flussi incoraggiano un accoppiamento più lento . Il codice di gestione del flusso non deve conoscere l'origine del flusso o il suo eventuale metodo di chiusura.

I flussi possono esprimere in modo succinto comportamenti piuttosto sofisticati . Per esempio:

 stream.filter(myfilter).findFirst();

Potrebbe sembrare a prima vista come se filtra l'intero flusso, quindi restituisce il primo elemento. Ma in realtà findFirst()guida l'intera operazione, quindi si arresta in modo efficiente dopo aver trovato un elemento.

I flussi offrono spazio per futuri guadagni di efficienza . Alcune persone hanno eseguito il benchmarking e hanno scoperto che i flussi a thread singolo da Listarray o memorie in memoria possono essere più lenti del loop equivalente. Questo è plausibile perché ci sono più oggetti e spese generali in gioco.

Ma i flussi si ridimensionano. Oltre al supporto integrato di Java per le operazioni di flusso parallelo, ci sono alcune librerie per la riduzione distribuita della mappa usando Streams come API, perché il modello si adatta.

Svantaggi?

Prestazioni : un forloop attraverso un array è estremamente leggero sia in termini di heap che di utilizzo della CPU. Se la velocità grezza e la parsimonia della memoria sono una priorità, l'uso di un flusso è peggio.

Familiarità . Il mondo è pieno di programmatori procedurali esperti, provenienti da molti contesti linguistici, per i quali i loop sono familiari e i flussi sono nuovi. In alcuni ambienti, vuoi scrivere un codice familiare a quel tipo di persona.

Sovraccarico cognitivo . A causa della sua natura dichiarativa e della maggiore astrazione da ciò che sta accadendo sotto, potrebbe essere necessario costruire un nuovo modello mentale di come il codice si collega all'esecuzione. In realtà, devi farlo solo quando le cose vanno male o se devi analizzare in profondità le prestazioni o i bug sottili. Quando "funziona", funziona.

I debugger stanno migliorando, ma anche ora, quando si passa attraverso il codice di flusso in un debugger, può essere più difficile del ciclo equivalente, perché un ciclo semplice è molto vicino alle variabili e alle posizioni di codice con cui funziona un debugger tradizionale.


4
Penso che sarebbe giusto chiarire che le cose simili a stream stanno diventando molto più comuni e ora si presentano in molte lingue comunemente usate che non sono particolarmente orientate alle FP.
Casey,

5
Dati i pro ei contro elencati qui, penso che gli stream NON valgano la pena per scopi diversi da quelli molto semplici (poca logica se / allora / altro, non molte chiamate nidificate o lambda, ecc.), In parti di prestazioni non critiche
Henrik Kjus Alstad

1
@HenrikKjusAlstad Non è assolutamente quello che intendevo comunicare. Gli stream sono maturi, potenti, espressivi e completamente appropriati per il codice di produzione.
magro

Oh, non intendevo dire che non lo avrei usato in produzione. Ma piuttosto, per impostazione predefinita, farei loop / ifs vecchio stile, piuttosto che stream, soprattutto se il flusso risultante sembrerebbe complesso. Sono sicuro che ci sono usi in cui uno stream batte i loop e, se è chiaro, ma il più delle volte, penso che sia "legato", o talvolta anche viceversa. Quindi, darei peso all'argomento cognitivo ambientale per attenersi ai vecchi modi.
Henrik Kjus Alstad,

1
@lijepdam - ma avresti ancora il codice che dice "Sto iterando su questo elenco (vedi all'interno del ciclo per scoprire perché)", quando "iterare sull'elenco" non è lo scopo principale del codice.
magro

16

Divertimento sintattico a parte, gli stream sono progettati per funzionare con set di dati potenzialmente infinitamente grandi, mentre array, raccolte e quasi tutte le classi Java SE che implementano Iterable sono interamente in memoria.

Uno svantaggio di uno stream è che i filtri, i mapping, ecc. Non possono generare eccezioni verificate. Ciò rende uno Stream una scelta inadeguata, ad esempio, per operazioni di I / O intermedie.


7
Ovviamente, puoi anche ricorrere a infinite fonti.
magro

Ma se gli elementi da elaborare sono persistenti in un DB, come si usano gli stream? Uno sviluppatore junior potrebbe essere tentato di leggerli tutti in una raccolta solo per usare gli stream. E sarebbe un disastro.
Lluis Martinez,

2
@LluisMartinez una buona libreria client DB restituirà qualcosa del genere Stream<Row>- o sarebbe possibile scrivere la propria Streamimplementazione avvolgendo le operazioni del cursore del risultato DB.
magro

Che gli stream ignorino silenziosamente le eccezioni è un bug da cui sono stato recentemente morso. Poco intuitivo.
xxfelixxx,

@xxfelixxx I flussi non ignorano silenziosamente le eccezioni. Prova a eseguire questo:Arrays.asList("test", null).stream().forEach(s -> System.out.println(s.length()));
VGR il

8
  1. Hai capito male: le operazioni parallele usano Streams, non Optionals.

  2. È possibile definire metodi che funzionano con i flussi: prenderli come parametri, restituirli, ecc. Non è possibile definire un metodo che accetta un ciclo come parametro. Ciò consente una complicata operazione di streaming una volta e lo utilizza più volte. Nota che Java ha uno svantaggio qui: i tuoi metodi devono essere chiamati in someMethod(stream)opposizione a quelli dello stream stream.someMethod(), quindi mescolarli complica la lettura: prova a vedere l'ordine delle operazioni in

    myMethod2(myMethod(stream.transform(...)).filter(...))

    Molte altre lingue (C #, Kotlin, Scala, ecc.) Consentono una qualche forma di "metodi di estensione".

  3. Anche quando sono necessarie solo operazioni sequenziali e non si desidera riutilizzarle, in modo da poter utilizzare flussi o loop, le semplici operazioni sui flussi potrebbero corrispondere a modifiche piuttosto complesse nei loop.


Spiegazione 1. L'interfaccia opzionale non è il mezzo con cui i null vengono gestiti in catene? Per quanto riguarda 3, questo ha senso perché con i filtri in corto circuito, il metodo verrà invocato solo per occorrenze specificate. Efficiente. È logico affermare che il loro utilizzo riduce la necessità di scrivere un codice aggiuntivo che dovrà essere testato, ecc. Dopo la revisione, non sono sicuro di cosa intendi per caso sequenziale in 2.
user447607

1. Optionalè un'alternativa a null, ma non ha nulla a che fare con le operazioni parallele. A meno che "Ora mi rendo conto che stavo pensando agli Optionals" nella tua domanda si tratta solo di nullgestire?
Alexey Romanov,

Ho cambiato l'ordine di 2 e 3 e li ho ampliati un po '.
Alexey Romanov,

6

Si esegue il ciclo su una sequenza (matrice, raccolta, input, ...) perché si desidera applicare una funzione agli elementi della sequenza.

Gli stream ti danno la possibilità di comporre funzioni su elementi di sequenza e ti permettono di implementare le funzioni più comuni (es. Mappatura, filtro, ricerca, ordinamento, raccolta, ...) indipendentemente da un caso concreto.

Pertanto, dato che alcune attività di looping nella maggior parte dei casi è possibile esprimerlo con meno codice utilizzando gli stream, ovvero ottenere leggibilità .


4
Bene, non è solo leggibilità però. Il codice che non devi scrivere è il codice che non devi testare.
user447607,

3
sembra che tu stia
trovando

6

Direi che la sua parallelizzazione è così facile da usare. Prova a ripetere più di milioni di voci in parallelo con un ciclo for. Andiamo a molti cpus, non più velocemente; quindi più è facile correre in parallelo meglio è, e con Streams questo è un gioco da ragazzi.

Quello che mi piace molto è la verbosità che offrono. Ci vuole poco tempo per capire cosa fanno realmente e produrre al contrario di come lo fanno.

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.