Perché dovrei usare le "operazioni funzionali" invece di un ciclo for?


39
for (Canvas canvas : list) {
}

NetBeans mi suggerisce di usare "operazioni funzionali":

list.stream().forEach((canvas) -> {
});

Ma perché è preferito ? Semmai, è più difficile da leggere e capire. Stai chiamando stream(), quindi stai forEach()usando un'espressione lambda con parametro canvas. Non vedo come sia più bello del forloop nel primo frammento.

Ovviamente sto parlando solo di estetica. Forse qui c'è un vantaggio tecnico che mi manca. Che cos'è? Perché invece dovrei usare il secondo metodo?



15
Nel tuo esempio particolare, non sarebbe preferito.
Robert Harvey,

1
Finché l'unica operazione è una singola per ciascuna, tendo ad essere d'accordo con te. Non appena si aggiungono altre operazioni alla pipeline o si produce una sequenza di output, diventa preferibile l'approccio al flusso.
Jacques B

@RobertHarvey no? perchè no?
Sara

@RobertHarvey Sono d'accordo che la risposta accettata mostra davvero come la versione for viene espulsa dall'acqua per casi più complicati, ma non vedo perché per "vittorie" nel caso banale. lo dichiari come evidente, ma io non lo vedo, quindi ho chiesto.
Sara

Risposte:


45

Gli stream forniscono un'astrazione molto migliore per la composizione di diverse operazioni che si desidera eseguire oltre alle raccolte o ai flussi di dati in arrivo. Soprattutto quando è necessario mappare elementi, filtrarli e convertirli.

Il tuo esempio non è molto pratico. Considerare il seguente codice dal sito Oracle .

List<Transaction> groceryTransactions = new Arraylist<>();
for(Transaction t: transactions){
  if(t.getType() == Transaction.GROCERY){
    groceryTransactions.add(t);
  }
}
Collections.sort(groceryTransactions, new Comparator(){
  public int compare(Transaction t1, Transaction t2){
    return t2.getValue().compareTo(t1.getValue());
  }
});
List<Integer> transactionIds = new ArrayList<>();
for(Transaction t: groceryTransactions){
  transactionsIds.add(t.getId());
}

può essere scritto usando i flussi:

List<Integer> transactionsIds = 
    transactions.stream()
                .filter(t -> t.getType() == Transaction.GROCERY)
                .sorted(comparing(Transaction::getValue).reversed())
                .map(Transaction::getId)
                .collect(toList());

La seconda opzione è molto più leggibile. Quindi, quando hai loop nidificati o vari loop che eseguono un'elaborazione parziale, è un ottimo candidato per l'utilizzo dell'API Streams / Lambda.


5
Esistono tecniche di ottimizzazione come map fusion ( stream.map(f).map(g)stream.map(f.andThen(g))) per creare / ridurre la fusione (quando si crea un flusso in un metodo e poi lo si passa a un altro metodo che lo consuma, il compilatore può eliminare il flusso) e la fusione del flusso (che può fondere molte operazioni del flusso insieme in un unico ciclo imperativo), che può rendere le operazioni di streaming molto più efficienti. Sono implementati nel compilatore GHC Haskell, e anche in alcuni altri compilatori di linguaggio funzionale Haskell e altri, e ci sono implementazioni di ricerca sperimentale per Scala.
Jörg W Mittag,

Le prestazioni probabilmente non sono un fattore quando si considera il ciclo funzionale vs poiché sicuramente il compilatore potrebbe / dovrebbe fare la conversione da un ciclo for a un'operazione funzionale se Netbeans può farlo e si ritiene che sia il percorso ottimale.
Ryan,

Non sarei d'accordo sul fatto che il secondo sia più leggibile. Ci vuole un po 'per capire cosa sta succedendo. Esiste un vantaggio prestazionale nel farlo secondo metodo perché altrimenti non lo vedo?
Bok McDonagh, il

L'esperienza di @BokMcDonagh, Me è che è meno leggibile per gli sviluppatori che non si sono preoccupati di familiarizzare con le nuove astrazioni. Vorrei suggerire di utilizzare maggiormente tali API, per acquisire maggiore familiarità, perché è il futuro. Non solo nel mondo Java.
luboskrnac il

16

Un altro vantaggio dell'utilizzo dell'API di streaming funzionale è che nasconde i dettagli di implementazione. Descrive solo cosa si dovrebbe fare, non come. Questo vantaggio diventa evidente quando si guarda al cambiamento che deve essere fatto, per passare dall'esecuzione di un singolo thread al codice parallelo. Basta cambiare il .stream()a .parallelStream().


13

Semmai, è più difficile da leggere e capire.

Questo è altamente soggettivo. Trovo la seconda versione molto più facile da leggere e capire. Corrisponde al modo in cui lo fanno altre lingue (ad es. Ruby, Smalltalk, Clojure, Io, Ioke, Seph), richiede meno concetti per capirlo (è solo una normale chiamata di metodo come qualsiasi altra, mentre il primo esempio è una sintassi specializzata).

Semmai, è una questione di familiarità.


6
Sì è vero. Ma questo sembra più un commento che una risposta per me.
Omega,

L'operazione funzionale utilizza anche una sintassi specializzata: il "->" è nuovo
Ryan l'
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.