Usare le espressioni Lambda quando possibile nelle buone pratiche di Java?


52

Di recente ho imparato l'espressione Lambda che è stata introdotta in Java 8. Trovo che ogni volta che utilizzo un'interfaccia funzionale, tendo sempre a usare un'espressione Lambda invece di creare una classe che implementa l'interfaccia funzionale.

Questa è considerata una buona pratica? O le loro situazioni in cui l'uso di una Lambda per un'interfaccia funzionale non è appropriato?


122
Alla domanda "Sta usando la tecnica X ogni volta che è possibile una buona pratica" l'unica risposta corretta è "no, usa la tecnica X non quando possibile, ma ogni volta che è ragionevole" .
Doc Brown,

La scelta di quando usare un lambda (che è anonimo) rispetto ad altri tipi di implementazione funzionale (ad esempio un riferimento al metodo) è una domanda davvero interessante. Ho avuto l'opportunità di incontrare Josh Bloch un paio di giorni fa, e questo è stato uno dei punti di cui ha parlato. Da quello che ha detto capisco che sta pianificando di aggiungere un nuovo oggetto alla prossima edizione di Effective Java che si occupa di questa domanda esatta.
Daniel Pryden,

@DocBrown Dovrebbe essere una risposta, non un commento.
gnasher729,

1
@ gnasher729: sicuramente no.
Doc Brown,

Risposte:


116

Esistono diversi criteri che dovrebbero farti considerare di non utilizzare un lambda:

  • Dimensione Più una lambda diventa grande, più diventa difficile seguire la logica che la circonda.
  • Ripetizione È meglio creare una funzione con nome per la logica ripetuta, anche se va bene ripetere lambda molto semplici che si dividono.
  • Denominazione Se riesci a pensare a un grande nome semantico, dovresti invece usarlo, poiché aggiunge molta chiarezza al tuo codice. Non sto parlando di nomi come priceIsOver100. x -> x.price > 100è chiaro come quel nome. Intendo nomi come isEligibleVoterquelli sostituiscono un lungo elenco di condizioni.
  • Nidificazione I lambda nidificati sono davvero, davvero difficili da leggere.

Non esagerare. Ricorda, il software può essere facilmente modificato. In caso di dubbi, scrivilo in entrambi i modi e vedi quale è più facile da leggere.


1
È anche importante considerare la quantità di dati che devono essere elaborati attraverso la lambda, poiché le lambda sono piuttosto inefficienti in piccole quantità di elaborazione dei dati. Brillano davvero nella parallelizzazione di grandi set di dati.
CraigR8806,

1
@ CraigR8806 Non penso che le prestazioni siano un problema qui. L'uso di una funzione anonima o la creazione di una classe che estende l'interfaccia funzionale dovrebbe avere le stesse prestazioni. Le considerazioni sulle prestazioni arrivano quando si parla dell'utilizzo di API stream / di ordine superiore su un loop di stile imperativo, ma in questa domanda si utilizzano interfacce funzionali in entrambi i casi solo con sintassi diversa.
Puhlen,

17
"anche se va bene ripetere lambda molto semplici che si dividono" Solo se non cambiano necessariamente insieme. Se devono essere tutti la stessa logica affinché il codice sia corretto, è necessario renderli tutti effettivamente lo stesso metodo in modo che le modifiche debbano essere apportate solo in un unico posto.
jpmc26,

Nel complesso questa è davvero una buona risposta. Menzionando l'uso di un riferimento al metodo in alternativa a un lambda si aggiusterebbe solo al buco che vedo in questa risposta.
Morgen,

3
In caso di dubbi, scrivilo in entrambi i modi e chiedi a qualcun altro che mantiene il codice più facile da leggere.
corsiKa

14

Dipende. Ogni volta che ti ritrovi a utilizzare la stessa lambda in luoghi diversi, dovresti prendere in considerazione l'implementazione di una classe che implementa l'interfaccia. Ma se avessi usato una classe interna anonima, altrimenti penso che una lambda sia molto meglio.


6
Non dimenticare la possibilità di utilizzare un riferimento al metodo! Oracle ha aggiunto (e ne sta aggiungendo ancora di più in Java 9) metodi statici e metodi predefiniti ovunque per questo motivo.
Jörg W Mittag,

13

Sostengo la risposta di Karl Bielefeldt, ma voglio fornire una breve aggiunta.

  • Debug Alcuni IDE lottano con l'ambito all'interno di un lambda e fanno fatica a visualizzare le variabili membro all'interno del contesto di un lambda. Mentre speriamo che questa situazione cambierà in futuro, può essere fastidioso mantenere il codice di qualcun altro quando è disseminato di lambda.

Sicuramente vero per .NET. Non mi fa evitare lambda, necessariamente, ma certamente non provo alcun senso di colpa se invece faccio qualcos'altro.
user1172763,

3
In questo caso stai usando l'IDE sbagliato. Sia IntelliJ che Netbeans si comportano piuttosto bene in quella particolare area.
David Foerster,

1
@ user1172763 In Visual Studio, se si desidera visualizzare le variabili membro, ho scoperto che è possibile spostarsi nello stack di chiamate fino ad arrivare al contesto del lambda.
Zev Spitz,

6

Accesso alle variabili locali dell'ambito allegato

La risposta accettata da Karl Bielefeldt è corretta. Posso aggiungere un'altra distinzione:

  • Scopo

Il codice lambda nidificato all'interno di un metodo all'interno di una classe può accedere a qualsiasi variabile efficacemente finale trovata all'interno di quel metodo e classe.

La creazione di una classe che implementa l'interfaccia funzionale non ti dà tale accesso diretto allo stato del codice chiamante.

Per citare il Tutorial Java (enfasi sul mio):

Come le classi locali e anonime, le espressioni lambda possono catturare variabili; hanno lo stesso accesso alle variabili locali dell'ambito allegato . Tuttavia, a differenza delle classi locali e anonime, le espressioni lambda non presentano alcun problema di ombreggiatura (vedere Ombreggiatura per ulteriori informazioni). Le espressioni lambda hanno un ambito lessicale. Ciò significa che non ereditano alcun nome da un supertipo né introducono un nuovo livello di ambito. Le dichiarazioni in un'espressione lambda vengono interpretate esattamente come nell'ambiente che la racchiude.

Quindi, mentre ci sono vantaggi nell'estrarre il codice lungo e nominarlo, è necessario valutarlo rispetto alla semplicità dell'accesso diretto allo stato del metodo e della classe inclusi.

Vedere:


4

Questo potrebbe essere un pignolo, ma a tutti gli altri punti eccellenti fatti in altre risposte, aggiungerei:

Preferire i riferimenti ai metodi quando possibile. Confrontare:

employees.stream()
         .map(Employee::getName)
         .forEach(System.out::println);

contro

employees.stream()
         .map(employee -> employee.getName())
         .forEach(employeeName -> System.out.println(employeeName));

L'uso di un riferimento al metodo consente di risparmiare la necessità di nominare gli argomenti della lambda, che di solito sono ridondanti e / o portano a nomi pigri come eo x.


0

Basandosi sulla risposta di Matt McHenry, può esserci una relazione tra lambda e riferimenti al metodo in cui il lambda può essere riscritto come una serie di riferimenti al metodo. Per esempio:

employees.stream()
         .forEach(employeeName -> System.out.println(employee.getName()));

vs

employees.stream()
         .map(Employee::getName)
         .forEach(System.out::println);
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.