JavaRush /Java Blog /Random-IT /Espressioni Lambda con esempi

Espressioni Lambda con esempi

Pubblicato nel gruppo Random-IT
Java è inizialmente un linguaggio completamente orientato agli oggetti. Ad eccezione dei tipi primitivi, tutto in Java è un oggetto. Anche gli array sono oggetti. Le istanze di ciascuna classe sono oggetti. Non esiste un'unica possibilità di definire una funzione separatamente (al di fuori di una classe - trad. ca. ). E non c'è modo di passare un metodo come argomento o restituire il corpo del metodo come risultato di un altro metodo. È come questo. Ma questo accadeva prima di Java 8. Espressioni Lambda con esempi - 1Sin dai tempi del buon vecchio Swing, era necessario scrivere classi anonime quando era necessario trasferire alcune funzionalità a qualche metodo. Ad esempio, questo è l'aspetto dell'aggiunta di un gestore eventi:
someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

                //Event listener implementation goes here...

            }
        });
Qui vogliamo aggiungere del codice al listener di eventi del mouse. Abbiamo definito una classe anonima MouseAdaptere da essa abbiamo immediatamente creato un oggetto. In questo modo, abbiamo trasferito funzionalità aggiuntive nel file addMouseListener. In breve, non è semplice passare un semplice metodo (funzionalità) in Java tramite argomenti. Questa limitazione ha costretto gli sviluppatori Java 8 ad aggiungere una funzionalità come le espressioni Lambda alle specifiche del linguaggio.

Perché Java ha bisogno delle espressioni Lambda?

Fin dall'inizio, il linguaggio Java non si è evoluto molto, ad eccezione di cose come Annotazioni, Generici, ecc. Innanzitutto, Java è sempre rimasto orientato agli oggetti. Dopo aver lavorato con linguaggi funzionali come JavaScript, si può capire come Java sia strettamente orientato agli oggetti e fortemente tipizzato. Le funzioni non sono necessarie in Java. Da soli, non possono essere trovati nel mondo Java. Nei linguaggi di programmazione funzionale, le funzioni vengono in primo piano. Esistono da soli. Puoi assegnarli a variabili e passarli tramite argomenti ad altre funzioni. JavaScript è uno dei migliori esempi di linguaggi di programmazione funzionale. Puoi trovare buoni articoli su Internet che descrivono in dettaglio i vantaggi di JavaScript come linguaggio funzionale. I linguaggi funzionali dispongono di strumenti potenti come Closure, che offrono numerosi vantaggi rispetto ai metodi tradizionali di scrittura delle applicazioni. Una chiusura è una funzione a cui è collegato un ambiente: una tabella che memorizza i riferimenti a tutte le variabili non locali della funzione. In Java, le chiusure possono essere simulate tramite espressioni Lambda. Naturalmente, ci sono differenze tra le chiusure e le espressioni Lambda, e non piccole, ma le espressioni lambda sono una buona alternativa alle chiusure. Nel suo sarcastico e divertente blog, Steve Yegge descrive come il mondo di Java sia strettamente legato ai sostantivi (entità, oggetti - trad. ca. ). Se non hai letto il suo blog, te lo consiglio. Descrive in modo divertente e interessante il motivo esatto per cui le espressioni Lambda sono state aggiunte a Java. Le espressioni Lambda apportano a Java funzionalità che mancavano da così tanto tempo. Le espressioni Lambda apportano funzionalità al linguaggio proprio come gli oggetti. Sebbene ciò non sia vero al 100%, puoi vedere che le espressioni Lambda, pur non essendo chiusure, forniscono funzionalità simili. In un linguaggio funzionale, le espressioni lambda sono funzioni; ma in Java, le espressioni lambda sono rappresentate da oggetti e devono essere associate a un tipo di oggetto specifico chiamato interfaccia funzionale. Successivamente vedremo di cosa si tratta. L'articolo di Mario Fusco “Perché abbiamo bisogno dell'espressione Lambda in Java” descrive in dettaglio perché tutti i linguaggi moderni necessitano di capacità di chiusura.

Introduzione alle espressioni Lambda

Le espressioni Lambda sono funzioni anonime (potrebbe non essere una definizione corretta al 100% per Java, ma apporta una certa chiarezza). In poche parole, questo è un metodo senza dichiarazione, ad es. senza modificatori di accesso, restituendo valore e nome. In breve, ti permettono di scrivere un metodo e di usarlo immediatamente. È particolarmente utile nel caso di una chiamata al metodo una tantum, perché riduce il tempo necessario per dichiarare e scrivere un metodo senza dover creare una classe. Le espressioni Lambda in Java in genere hanno la seguente sintassi (аргументы) -> (тело). Per esempio:
(арг1, арг2...) -> { тело }

(тип1 арг1, тип2 арг2...) -> { тело }
Di seguito sono riportati alcuni esempi di espressioni Lambda reali:
(int a, int b) -> {  return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

Struttura delle espressioni Lambda

Studiamo la struttura delle espressioni lambda:
  • Le espressioni lambda possono avere 0 o più parametri di input.
  • Il tipo di parametri può essere specificato esplicitamente o può essere ottenuto dal contesto. Ad esempio ( int a) può essere scritto in questo modo ( a)
  • I parametri sono racchiusi tra parentesi e separati da virgole. Ad esempio ( a, b) o ( int a, int b) o ( String a, int b, float c)
  • Se non sono presenti parametri, è necessario utilizzare parentesi vuote. Per esempio() -> 42
  • Quando è presente un solo parametro, se il tipo non è specificato esplicitamente, le parentesi possono essere omesse. Esempio:a -> return a*a
  • Il corpo di un'espressione Lambda può contenere 0 o più espressioni.
  • Se il corpo è costituito da una singola istruzione, non può essere racchiuso tra parentesi graffe e il valore restituito può essere specificato senza la parola chiave return.
  • Altrimenti sono obbligatorie le parentesi graffe (blocco di codice) e il valore restituito deve essere specificato alla fine tramite una parola chiave return(altrimenti il ​​tipo restituito sarà void).

Che cos'è un'interfaccia funzionale

In Java, le interfacce Marker sono interfacce senza dichiarare metodi o campi. In altre parole, le interfacce token sono interfacce vuote. Allo stesso modo, le interfacce funzionali sono interfacce con un solo metodo astratto dichiarato su di essa. java.lang.Runnableè un esempio di interfaccia funzionale. Dichiara solo un metodo void run(). C'è anche un'interfaccia ActionListener, anch'essa funzionale. In precedenza, dovevamo utilizzare classi anonime per creare oggetti che implementassero un'interfaccia funzionale. Con le espressioni Lambda tutto è diventato più semplice. Ogni espressione lambda può essere implicitamente associata ad alcune interfacce funzionali. Ad esempio, puoi creare un riferimento a Runnableun'interfaccia, come mostrato nell'esempio seguente:
Runnable r = () -> System.out.println("hello world");
Questo tipo di conversione viene sempre eseguita implicitamente quando non specifichiamo un'interfaccia funzionale:
new Thread(
    () -> System.out.println("hello world")
).start();
Nell'esempio sopra, il compilatore crea automaticamente un'espressione lambda come implementazione Runnabledell'interfaccia dal costruttore della classe Thread: public Thread(Runnable r) { }. Ecco alcuni esempi di espressioni lambda e corrispondenti interfacce funzionali:
Consumer<Integer> c = (int x) -> { System.out.println(x) };

BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);

Predicate<String> p = (String s) -> { s == null };
L'annotazione @FunctionalInterfaceaggiunta in Java 8 secondo la Java Language Specifica verifica se l'interfaccia dichiarata è funzionale. Inoltre, Java 8 include una serie di interfacce funzionali già pronte da utilizzare con le espressioni Lambda. @FunctionalInterfacegenererà un errore di compilazione se l'interfaccia dichiarata non funziona. Di seguito è riportato un esempio di definizione di un'interfaccia funzionale:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
Come suggerisce la definizione, un'interfaccia funzionale può avere un solo metodo astratto. Se provi ad aggiungere un altro metodo astratto, riceverai un errore di compilazione. Esempio:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

    public void doSomeMoreWork();

}
Errore
Unexpected @FunctionalInterface annotation
    @FunctionalInterface ^ WorkerInterface is not a functional interface multiple
    non-overriding abstract methods found in interface WorkerInterface 1 error
После определения функционального интерфейса, мы можем его использовать и получать все преимущества Lambda-выражений. Пример:// defining a functional interface
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
public class WorkerInterfaceTest {

    public static void execute(WorkerInterface worker) {
        worker.doSomeWork();
    }

    public static void main(String [] args) {

      // calling the doSomeWork method via an anonymous class
      // (classic)
      execute(new WorkerInterface() {
            @Override
            public void doSomeWork() {
               System.out.println("Worker called via an anonymous class");
            }
        });

      // calling the doSomeWork method via Lambda expressions
      // (Java 8 new)
      execute( () -> System.out.println("Worker called via Lambda") );
    }

}
Conclusione:
Worker вызван через анонимный класс
Worker вызван через Lambda
Qui abbiamo definito la nostra interfaccia funzionale e utilizzato un'espressione lambda. Il metodo execute()è in grado di accettare espressioni lambda come argomento.

Esempi di espressioni Lambda

Il modo migliore per comprendere le espressioni Lambda è osservare alcuni esempi: un flusso Threadpuò essere inizializzato in due modi:
// Old way:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread");
    }
}).start();
// New way:
new Thread(
    () -> System.out.println("Hello from thread")
).start();
La gestione degli eventi in Java 8 può essere eseguita anche tramite espressioni Lambda. Di seguito sono riportati due modi per aggiungere un gestore eventi ActionListenera un componente dell'interfaccia utente:
// Old way:
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button pressed. Old way!");
    }
});
// New way:
button.addActionListener( (e) -> {
        System.out.println("Button pressed. Lambda!");
});
Un semplice esempio di visualizzazione di tutti gli elementi di un determinato array. Tieni presente che esiste più di un modo per utilizzare un'espressione lambda. Di seguito creiamo un'espressione lambda nel solito modo utilizzando la sintassi della freccia e utilizziamo anche l'operatore dei due punti (::), che in Java 8 converte un metodo regolare in un'espressione lambda:
// Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
    System.out.println(n);
}
// New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// New way using double colon operator ::
list.forEach(System.out::println);
Nell'esempio seguente, utilizziamo un'interfaccia funzionale Predicateper creare un test e stampare gli elementi che superano tale test. In questo modo puoi inserire la logica nelle espressioni lambda e fare cose basate su di essa.
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Main {

    public static void main(String [] a)  {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

        System.out.print("Outputs all numbers: ");
        evaluate(list, (n)->true);

        System.out.print("Does not output any number: ");
        evaluate(list, (n)->false);

        System.out.print("Output even numbers: ");
        evaluate(list, (n)-> n%2 == 0 );

        System.out.print("Output odd numbers: ");
        evaluate(list, (n)-> n%2 == 1 );

        System.out.print("Output numbers greater than 5: ");
        evaluate(list, (n)-> n > 5 );

    }

    public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
        for(Integer n: list)  {
            if(predicate.test(n)) {
                System.out.print(n + " ");
            }
        }
        System.out.println();
    }

}
Conclusione:
Выводит все числа: 1 2 3 4 5 6 7
Не выводит ни одного числа:
Вывод четных чисел: 2 4 6
Вывод нечетных чисел: 1 3 5 7
Вывод чисел больше 5: 6 7
Armeggiando con le espressioni Lambda, puoi visualizzare il quadrato di ciascun elemento dell'elenco. Nota che stiamo utilizzando il metodo stream()per convertire un elenco normale in un flusso. Java 8 fornisce una classe fantastica Stream( java.util.stream.Stream). Contiene tantissimi metodi utili con cui puoi utilizzare le espressioni lambda. Passiamo un'espressione lambda x -> x*xal metodo map(), che la applica a tutti gli elementi nello stream. Dopodiché usiamo forEachper stampare tutti gli elementi della lista.
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
    int x = n * n;
    System.out.println(x);
}
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
Data una lista, è necessario stampare la somma dei quadrati di tutti gli elementi della lista. Le espressioni Lambda ti consentono di raggiungere questo obiettivo scrivendo solo una riga di codice. Questo esempio utilizza il metodo di convoluzione (riduzione) reduce(). Utilizziamo un metodo map()per quadrare ciascun elemento, quindi utilizziamo un metodo reduce()per comprimere tutti gli elementi in un singolo numero.
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
    int x = n * n;
    sum = sum + x;
}
System.out.println(sum);
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);

La differenza tra espressioni Lambda e classi anonime

La differenza principale è l'uso della parola chiave this. Per le classi anonime, la parola chiave '' thisdenota un oggetto della classe anonima, mentre in un'espressione lambda, ' this' denota un oggetto della classe in cui viene utilizzata l'espressione lambda. Un'altra differenza è il modo in cui vengono compilati. Java compila espressioni lambda e le converte in privatemetodi di classe. Utilizza l' istruzione invokedynamic , introdotta in Java 7 per l'associazione dinamica del metodo. Tal Weiss ha descritto nel suo blog come Java compila le espressioni lambda in bytecode

Conclusione

Mark Reinhold (capo architetto di Oracle) ha definito le espressioni Lambda il cambiamento più significativo mai avvenuto nel modello di programmazione, persino più significativo dei generici. Deve avere ragione, perché... offrono ai programmatori Java le funzionalità del linguaggio di programmazione funzionale che tutti stavano aspettando. Insieme a innovazioni come i metodi di estensione virtuale, le espressioni Lambda ti consentono di scrivere codice di altissima qualità. Spero che questo articolo ti abbia dato uno sguardo dietro il cofano di Java 8. Buona fortuna :)
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION