Ritorno da lambda forEach () in java


95

Sto cercando di cambiare alcuni cicli for-each in lambda forEach()-methods per scoprire le possibilità delle espressioni lambda. Sembra possibile quanto segue:

ArrayList<Player> playersOfTeam = new ArrayList<Player>();      
for (Player player : players) {
    if (player.getTeam().equals(teamName)) {
        playersOfTeam.add(player);
    }
}

Con lambda forEach()

players.forEach(player->{if (player.getTeam().equals(teamName)) {playersOfTeam.add(player);}});

Ma il prossimo non funziona:

for (Player player : players) {
    if (player.getName().contains(name)) {
        return player;
    }
}

con lambda

players.forEach(player->{if (player.getName().contains(name)) {return player;}});

C'è qualcosa di sbagliato nella sintassi dell'ultima riga o è impossibile tornare dal forEach()metodo?


Non ho ancora molta familiarità con gli interni dei lambda, ma quando mi pongo la domanda: "Da cosa torneresti?", Il mio sospetto iniziale sarebbe che non è il metodo.
Gimby

1
@Gimby Sì, returnall'interno di un'istruzione lambda ritorna dalla lambda stessa, non da qualunque cosa sia chiamata lambda. Termina presto un flusso ("cortocircuito") findFirstcome mostrato nella risposta di Ian Roberts .
Stuart Marks

Risposte:


118

Il returnritorno dall'espressione lambda invece che dal metodo contenitore. Invece di forEachaver bisogno filterdello stream:

players.stream().filter(player -> player.getName().contains(name))
       .findFirst().orElse(null);

Qui filterlimita il flusso a quegli elementi che corrispondono al predicato e findFirstquindi restituisce un Optionalcon la prima voce corrispondente.

Questo sembra meno efficiente dell'approccio for-loop, ma in realtà findFirst()può cortocircuitare: non genera l'intero flusso filtrato e quindi estrae un elemento da esso, piuttosto filtra solo tutti gli elementi necessari per trova il primo corrispondente. Puoi anche usare findAny()invece di findFirst()se non ti interessa necessariamente ottenere il primo giocatore corrispondente dallo stream (ordinato) ma semplicemente qualsiasi elemento corrispondente. Ciò consente una migliore efficienza quando è coinvolto il parallelismo.


Grazie, è quello che stavo cercando! Sembra che ci siano molte novità in Java8 da esplorare :)
samutamm

10
Ragionevole, ma ti suggerisco di non utilizzare orElse(null)su un file Optional. Il punto principale di Optionalè fornire un modo per indicare la presenza o l'assenza di un valore invece di sovraccaricare nullo (che porta a NPE). Se lo usi optional.orElse(null), riacquista tutti i problemi con i null. Lo userei solo se non puoi modificare il chiamante e si aspetta davvero un valore nullo.
Stuart Marks

1
@StuartMarks infatti, la modifica del tipo restituito dal metodo in Optional<Player>sarebbe un modo più naturale per adattarsi al paradigma dei flussi. Stavo solo cercando di mostrare come duplicare il comportamento esistente usando lambda.
Ian Roberts

for (Part part: parts) if (! part.isEmpty ()) restituisce false; Mi chiedo cosa sia veramente più breve. E più chiaro. L'API java stream ha veramente distrutto il linguaggio Java e l'ambiente Java. Incubo di lavorare in qualsiasi progetto Java nel 2020.
mmm

15

Ti suggerisco di provare prima a capire Java 8 in tutto il quadro, soprattutto nel tuo caso saranno stream, lambda e riferimenti a metodi.

Non dovresti mai convertire il codice esistente in codice Java 8 riga per riga, dovresti estrarre le funzionalità e convertirle.

Quello che ho identificato nel tuo primo caso è il seguente:

  • Si desidera aggiungere elementi di una struttura di input a un elenco di output se corrispondono a un predicato.

Vediamo come lo facciamo, possiamo farlo con quanto segue:

List<Player> playersOfTeam = players.stream()
    .filter(player -> player.getTeam().equals(teamName))
    .collect(Collectors.toList());

Quello che fai qui è:

  1. Trasforma la tua struttura di input in un flusso (presumo che qui sia di tipo Collection<Player>, ora hai un file Stream<Player>.
  2. Filtra tutti gli elementi indesiderati con un Predicate<Player>, mappando ogni giocatore sul booleano true se desideri mantenerlo.
  3. Raccogli gli elementi risultanti in un elenco, tramite a Collector, qui possiamo usare uno dei raccoglitori di libreria standard, che è Collectors.toList().

Questo include anche altri due punti:

  1. Codice contro interfacce, quindi codice contro List<E>oltre ArrayList<E>.
  2. Usa l'inferenza del diamante per il parametro del tipo in new ArrayList<>(), dopotutto stai usando Java 8.

Ora sul secondo punto:

Ancora una volta vuoi convertire qualcosa del vecchio Java in Java 8 senza guardare il quadro più grande. Questa parte ha già ricevuto risposta da @IanRoberts , anche se penso che tu debba fare players.stream().filter(...)...ciò che ha suggerito.


5

Se vuoi restituire un valore booleano, puoi usare qualcosa di simile (molto più veloce del filtro):

players.stream().anyMatch(player -> player.getName().contains(name));


1

Puoi anche generare un'eccezione:

Nota:

Per motivi di leggibilità, ogni passaggio del flusso dovrebbe essere elencato in una nuova riga.

players.stream()
       .filter(player -> player.getName().contains(name))
       .findFirst()
       .orElseThrow(MyCustomRuntimeException::new);

se la tua logica è vagamente "guidata dalle eccezioni", ad esempio c'è un punto nel codice che cattura tutte le eccezioni e decide cosa fare dopo. Usa lo sviluppo guidato dalle eccezioni solo quando puoi evitare di sporcare la tua base di codice con multipli try-catche lanciare queste eccezioni sono per casi molto speciali che ti aspetti e possono essere gestiti correttamente.)

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.