English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
In questo articolo, esploreremo le espressioni lambda Java tramite esempi, nonché l'uso delle interfacce funzionali, delle interfacce funzionali generiche e dell'API di stream.
Le espressioni lambda sono state introdotte per la prima volta in Java 8. Il loro principale obiettivo è migliorare la capacità espressiva del linguaggio.
Ma, prima di studiare i lambda, dobbiamo prima comprendere le interfacce funzionali.
Se un'interfaccia Java contiene solo un metodo abstracto, viene chiamata interfaccia funzionale. Solo questo metodo specifica l'uso previsto dell'interfaccia.
Ad esempio, l'interfaccia Runnable nel pacchetto java.lang è un'interfaccia funzionale perché contiene solo un metodo, ovvero run().
import java.lang.FunctionalInterface; @FunctionalInterface public interface MyInterface{ //Metodo abstracto singolo double getValue(); }
Nell'esempio sopra, l'interfaccia MyInterface ha un solo metodo abstracto getValue(). Pertanto, è un'interfaccia funzionale.
Ecco, abbiamo utilizzato l'annotazione @FunctionalInterface. Questa annotazione costringe il compilatore Java a indicare che l'interfaccia è un'interfaccia funzionale. Pertanto, non è permesso avere più metodi abstracti. Tuttavia, non è obbligatoria.
In Java 7, l'interfaccia funzionale viene considerataMetodo abstracto singolo (SAM)Il tipo. In Java 7, il tipo SAM è solitamente implementato tramite classi anonime.
public class FunctionInterfaceTest { public static void main(String[] args) { // Classe anonima new Thread(new Runnable() { @Override public void run() { System.out.println("Ho appena implementato l'interfaccia funzionale Runnable."); } }).start(); } }
Output:
Ho appena implementato l'interfaccia funzionale Runnable.
In questo caso, possiamo passare una classe anonima al metodo. Questo aiuta a scrivere meno codice con Java 7. Tuttavia, la sintassi è ancora difficile e richiede molte righe di codice aggiuntivo.
Java 8 ha esteso ulteriormente le funzionalità di SAM. Poiché sappiamo che un'interfaccia funzionale ha un solo metodo, non è necessario definire il nome del metodo quando lo si passa come parametro. L'espressione lambda ci permette di fare questo.
Un'espressione lambda è essenzialmente un metodo anonimo o senza nome. Un'espressione lambda non può essere eseguita singolarmente. Invece, viene utilizzata per implementare i metodi definiti dall'interfaccia funzionale.
Ecco come definiamo un'espressione lambda in Java.
(lista dei parametri) -> corpo della lambda
L'operatore utilizzato (->) è noto come operatore freccia o operatore lambda. Esploriamo alcuni esempi:
Supponiamo di avere un metodo del genere:
double getPiValue() { return 3.1415; }
Possiamo scrivere questo metodo utilizzando l'espressione lambda, come segue:
() -> 3.1415
In questo caso, il metodo non ha parametri. Pertanto, il lato sinistro dell'operatore include un parametro vuoto. Il lato destro è il corpo della lambda, utilizzato per specificare l'operazione dell'espressione lambda. In questo caso, restituirà il valore 3.1415.
In Java, il corpo della lambda ha due tipi.
1. Corpo dell'espressione singola
() -> System.out.println("Lambdas sono fantastici");
Questo tipo di corpo lambda si chiama corpo dell'espressione.}
2. Corpo composto da blocco di codice.
() -> { double pi = 3.1415; return pi; };
Questo tipo di corpo lambda si chiama blocco. Il corpo del blocco permette al corpo lambda di contenere più istruzioni. Queste istruzioni sono racchiuse tra parentesi graffe e devi aggiungere un punto e virgola alla fine.
Attenzione: Per il corpo del blocco, dovresti sempre avere una istruzione return. Tuttavia, un corpo dell'espressione singola non richiede una istruzione return.
Scriviamo un programma Java che utilizza un'espressione lambda per restituire il valore di Pi.
Come accennato in precedenza, le lambda espressioni non vengono eseguite singolarmente. Al contrario, formano l'implementazione dei metodi astratti definiti dall'interfaccia funzionale.
Dunque, dobbiamo definire prima un'interfaccia funzionale.
import java.lang.FunctionalInterface; //è un'interfaccia funzionale @FunctionalInterface interface MyInterface{ //metodo astratto double getPiValue(); } public class Main { public static void main(String[] args) { //dichiarazione del riferimento a MyInterface MyInterface ref; //espressione lambda ref = () -> 3.1415; System.out.println("Pi = " + ref.getPiValue()); } }
Output:
Pi = 3.1415
Nell'esempio sopra,
abbiamo creato un'interfaccia funzionale chiamata MyInterface. Contiene un metodo astratto chiamato getPiValue()
All'interno della classe Main, dichiariamo un riferimento a MyInterface. Nota che possiamo dichiarare un riferimento all'interfaccia, ma non possiamo istanziare l'interfaccia. Questo perché,
// lancia un errore MyInterface ref = new myInterface(); // è valido MyInterface ref;
Poi, assegniamo un'espressione lambda alla variabile di riferimento.
ref = () -> 3.1415;
Infine, chiamiamo il metodo getPiValue() dell'interfaccia reference.
System.out.println("Pi = " + ref.getPiValue());
Fino ad ora, abbiamo creato lambda espressioni senza parametri. Tuttavia, come i metodi, le lambda espressioni possono anche avere parametri. Ad esempio,
(n) -> (n % 2) == 0
In questo caso, la variabile n tra parentesi graffe è il parametro passato all'espressione lambda. Il corpo lambda accetta il parametro e verifica se è un numero pari o dispari.
@FunctionalInterface interface MyInterface { //metodo astratto String reverse(String n); } public class Main { public static void main(String[] args) { //dichiarazione del riferimento a MyInterface // assegna l'espressione lambda a un riferimento MyInterface ref = (str) -> { String result = ""; for (int i = str.length() - 1; i >= 0; i--) result += str.charAt(i); } return result; }; // chiama il metodo dell'interfaccia System.out.println("Lambda reversed = " + ref.reverse("Lambda")); } }
Output:
Lambda reversed = adbmaL
Fino ad ora, abbiamo utilizzato interfacce funzionali che accettano solo un tipo di valore. Ad esempio,
@FunctionalInterface interface MyInterface { String reverseString(String n); }
L'interfaccia funzionale sopra accetta solo String e restituisce String. Ma possiamo rendere l'interfaccia funzionale universale, in modo che accetti qualsiasi tipo di dati. Se non sei familiare con i generici, visitaGenerici Java.
// GenericInterface.java @FunctionalInterface interface GenericInterface<T> { // metodo generico T func(T t); } // GenericLambda.java public class Main { public static void main(String[] args) { // dichiara un riferimento a GenericInterface // GenericInterface opera sui dati String // per assegnare un'espressione lambda GenericInterface<String> reverse = (str) -> { String result = ""; for (int i = str.length() - 1; i >= 0; i--) result += str.charAt(i); return result; }; System.out.println("Lambda reversed = " + reverse.func("Lambda")); // dichiara un altro riferimento a GenericInterface // GenericInterface opera sui dati interi // per assegnare un'espressione lambda GenericInterface<Integer> factorial = (n) -> { int result = 1; for (int i = 1; i <= n; i++) result = i * result; return result; }; System.out.println("5! = " + factorial.func(5)); } }
Output:
Lambda reversed = adbmaL 5! = 120
Nell'esempio sopra, abbiamo creato un'interfaccia funzionale generica chiamata GenericInterface. Contiene un metodo generico chiamato func().
All'interno della classe:
GenericInterface<String> reverse - creare un riferimento a questo'interfaccia. Ora, l'interfaccia può gestire dati di tipo String.
GenericInterface<Integer> factorial - creare un riferimento a questo'interfaccia. In questo caso, l'interfaccia opera sui dati di tipo Integer.
Nuovojava.util.streamIl pacchetto è stato aggiunto a JDK8, che permette agli sviluppatori Java di eseguire operazioni di ricerca, filtraggio, mappatura, riduzione e altro, o operazioni su liste e altre collezioni.
Ad esempio, abbiamo un flusso di dati (in nostro esempio una lista di stringhe), in cui ogni stringa è una combinazione di nome di nazione e regione. Ora, possiamo elaborare questo flusso di dati e recuperare solo le posizioni dal Nepal.
Per questo, possiamo combinare l'uso di Stream API e espressioni lambda per eseguire operazioni di batch nel flusso.
import java.util.ArrayList; import java.util.List; public class StreamMain { //使用ArrayList创建一个列表对象 static List<String> places = new ArrayList<>(); //准备我们的数据 public static List getPlaces(){ //将地点和国家添加到列表中 places.add("Nepal, Kathmandu"); places.add("Nepal, Pokhara"); places.add("India, Delhi"); places.add("USA, New York"); places.add("Africa, Nigeria"); return places; } public static void main(String[] args) { List<String> myPlaces = getPlaces(); System.out.println("Luoghi da Nepal:"); myPlaces.stream() .filter((p) -> p.startsWith("Nepal")) .map((p) -> p.toUpperCase()) .sorted() .forEach((p) -> System.out.println(p)); } }
Output:
Luoghi da Nepal: NEPAL, KATHMANDU NEPAL, POKHARA
Nell'esempio sopra, nota le seguenti istruzioni:
myPlaces.stream() .filter((p) -> p.startsWith("Nepal")) .map((p) -> p.toUpperCase()) .sorted() .forEach((p) -> System.out.println(p));
In questo esempio, usiamo metodi come filter(), map() e forEach() dell'API Stream. Questi metodi possono accettare espressioni lambda come input.
Possiamo definire le nostre espressioni in base alla grammatica che abbiamo studiato. Come nell'esempio sopra, questo ci permette di ridurre notevolmente il numero di righe di codice.