Ricerca…


introduzione

Le espressioni Lambda forniscono un modo chiaro e conciso per implementare un'interfaccia a metodo singolo usando un'espressione. Ti permettono di ridurre la quantità di codice che devi creare e mantenere. Sebbene simili alle classi anonime, non hanno informazioni di tipo da soli. Il tipo di inferenza deve accadere.

I riferimenti al metodo implementano interfacce funzionali usando i metodi esistenti piuttosto che le espressioni. Appartengono anche alla famiglia Lambda.

Sintassi

  • () -> {restituisce espressione; } // Zero-arity con il corpo della funzione per restituire un valore.
  • () -> espressione // Abbreviazione per la dichiarazione di cui sopra; non esiste un punto e virgola per le espressioni.
  • () -> {function-body} // Effetto collaterale nell'espressione lambda per eseguire operazioni.
  • parameterName -> expression // Espressione lambda one-arity. Nelle espressioni lambda con un solo argomento, la parentesi può essere rimossa.
  • (Digitare parameterName, Type secondParameterName, ...) -> expression // lambda che valuta un'espressione con i parametri elencati a sinistra
  • (parameterName, secondParameterName, ...) -> expression // Stenografia che rimuove i tipi di parametri per i nomi dei parametri. Può essere utilizzato solo in contesti che possono essere dedotti dal compilatore in cui le dimensioni dell'elenco dei parametri corrispondono a una (e solo una) della dimensione delle interfacce funzionali previste.

Utilizzo delle espressioni Lambda per ordinare una raccolta

Liste di ordinamento

Prima di Java 8, era necessario implementare l'interfaccia java.util.Comparator con una classe anonima (o named) durante l'ordinamento di una lista 1 :

Java SE 1.2
List<Person> people = ...
Collections.sort(
    people,
    new Comparator<Person>() {
        public int compare(Person p1, Person p2){
            return p1.getFirstName().compareTo(p2.getFirstName());
        }
    }
);

A partire da Java 8, la classe anonima può essere sostituita con un'espressione lambda. Si noti che i tipi per i parametri p1 e p2 possono essere omessi, in quanto il compilatore li dedurrà automaticamente:

Collections.sort(
    people, 
    (p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName())
);

L'esempio può essere semplificato utilizzando Comparator.comparing e i riferimenti ai metodi espressi utilizzando il simbolo :: (doppio punto).

Collections.sort(
    people,
    Comparator.comparing(Person::getFirstName)
);

Un'importazione statica ci consente di esprimerlo in modo più conciso, ma è discutibile se questo migliora la leggibilità complessiva:

import static java.util.Collections.sort;
import static java.util.Comparator.comparing;
//...
sort(people, comparing(Person::getFirstName));

I comparatori costruiti in questo modo possono anche essere concatenati insieme. Ad esempio, dopo aver confrontato le persone con il loro nome, se ci sono persone con lo stesso nome, il metodo thenComparing con anche il confronto per cognome:

sort(people, comparing(Person::getFirstName).thenComparing(Person::getLastName));

1 - Si noti che Collections.sort (...) funziona solo su raccolte che sono sottotipi di List . Le API Set e Collection non implicano alcun ordine degli elementi.

Ordinare le mappe

È possibile ordinare le voci di una HashMap base al valore in modo simile. (Si noti che una LinkedHashMap deve essere utilizzata come destinazione. Le chiavi in ​​una HashMap ordinaria non sono ordinate.)

Map<String, Integer> map = new HashMap();  // ... or any other Map class
// populate the map
map = map.entrySet()
    .stream()
    .sorted(Map.Entry.<String, Integer>comparingByValue())
    .collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue(),
                              (k, v) -> k, LinkedHashMap::new));

Introduzione ai lambda Java

Interfacce funzionali

Lambdas può operare solo su un'interfaccia funzionale, che è un'interfaccia con un solo metodo astratto. Le interfacce funzionali possono avere un numero qualsiasi di metodi default o static . (Per questo motivo, a volte vengono definiti Interfacce di metodo astratto singolo o Interfacce SAM).

interface Foo1 {
    void bar();
}

interface Foo2 {
    int bar(boolean baz);
}

interface Foo3 {
    String bar(Object baz, int mink);
}

interface Foo4 {
    default String bar() { // default so not counted
        return "baz";
    }
    void quux();
}

Quando si dichiara un'interfaccia funzionale, è possibile aggiungere l'annotazione @FunctionalInterface . Ciò non ha alcun effetto speciale, ma verrà generato un errore del compilatore se questa annotazione viene applicata a un'interfaccia che non è funzionale, in modo da ricordare che l'interfaccia non deve essere modificata.

@FunctionalInterface
interface Foo5 {
    void bar();
}

@FunctionalInterface
interface BlankFoo1 extends Foo3 { // inherits abstract method from Foo3
}

@FunctionalInterface
interface Foo6 {
    void bar();
    boolean equals(Object obj); // overrides one of Object's method so not counted
}

Viceversa, questa non è un'interfaccia funzionale, poiché ha più di un metodo astratto :

interface BadFoo {
    void bar();
    void quux(); // <-- Second method prevents lambda: which one should 
                 // be considered as lambda?
}

Anche questa non è un'interfaccia funzionale, in quanto non ha alcun metodo:

interface BlankFoo2 { }

Prendi nota di quanto segue. Supponiamo di avere

interface Parent { public int parentMethod(); }

e

interface Child extends Parent { public int ChildMethod(); }

Quindi Child non può essere un'interfaccia funzionale poiché ha due metodi specificati.

Java 8 fornisce anche un certo numero di interfacce funzionali basate su modelli generici nel pacchetto java.util.function . Ad esempio, l'interfaccia incorporata Predicate<T> esegue il wrapping di un singolo metodo che immette un valore di tipo T e restituisce un valore boolean .


Lambda Expressions

La struttura di base di un'espressione Lambda è:

FunctionalInterface fi = () -> System.out.println ("Hello");

fi manterrà quindi un'istanza singleton di una classe, simile a una classe anonima, che implementa FunctionalInterface e in cui la definizione del metodo uno è { System.out.println("Hello"); } . In altre parole, quanto sopra è principalmente equivalente a:

FunctionalInterface fi = new FunctionalInterface() {
    @Override
    public void theOneMethod() {
        System.out.println("Hello");
    }
};

Il lambda è solo "per lo più equivalente" alla classe anonima perché in un lambda, il significato di espressioni come this , super o toString() riferimento alla classe in cui si svolge l'assegnazione, non all'oggetto appena creato.

Non è possibile specificare il nome del metodo quando si utilizza un lambda, ma non è necessario, perché un'interfaccia funzionale deve avere solo un metodo astratto, quindi Java lo sostituisce.

Nei casi in cui il tipo di lambda non è certo, (ad esempio metodi sovraccaricati) è possibile aggiungere un cast al lambda per dire al compilatore quale dovrebbe essere il suo tipo, in questo modo:

Object fooHolder = (Foo1) () -> System.out.println("Hello");
System.out.println(fooHolder instanceof Foo1); // returns true

Se il metodo singolo dell'interfaccia funzionale prende i parametri, i nomi formali locali di questi dovrebbero apparire tra le parentesi del lambda. Non è necessario dichiarare il tipo del parametro o return in quanto questi sono presi dall'interfaccia (sebbene non sia un errore dichiarare i tipi di parametro se lo si desidera). Quindi, questi due esempi sono equivalenti:

Foo2 longFoo = new Foo2() {
    @Override
    public int bar(boolean baz) {
        return baz ? 1 : 0;
    }
};
Foo2 shortFoo = (x) -> { return x ? 1 : 0; };

Le parentesi intorno all'argomento possono essere omesse se la funzione ha solo un argomento:

Foo2 np = x -> { return x ? 1 : 0; }; // okay
Foo3 np2 = x, y -> x.toString() + y // not okay

Ritorni impliciti

Se il codice inserito all'interno di una lambda è un'espressione Java anziché un'istruzione , viene considerato come un metodo che restituisce il valore dell'espressione. Quindi, i due seguenti sono equivalenti:

IntUnaryOperator addOneShort = (x) -> (x + 1);
IntUnaryOperator addOneLong = (x) -> { return (x + 1); };

Accesso alle variabili locali (chiusure di valore)

Poiché lambdas è una sintassi sintattica per le classi anonime, esse seguono le stesse regole per accedere alle variabili locali nell'ambito di inclusione; le variabili devono essere considerate come final e non modificate all'interno della lambda.

IntUnaryOperator makeAdder(int amount) {
    return (x) -> (x + amount); // Legal even though amount will go out of scope
                                // because amount is not modified
}

IntUnaryOperator makeAccumulator(int value) {
    return (x) -> { value += x; return value; }; // Will not compile
}

Se è necessario avvolgere una variabile variabile in questo modo, deve essere utilizzato un oggetto regolare che conserva una copia della variabile. Maggiori informazioni in Java Chiusure con espressioni lambda.


Accettare Lambdas

Poiché un lambda è un'implementazione di un'interfaccia, non occorre fare nulla di speciale per far sì che un metodo accetti un lambda: qualsiasi funzione che prende un'interfaccia funzionale può anche accettare un lambda.

public void passMeALambda(Foo1 f) {
    f.bar();
}
passMeALambda(() -> System.out.println("Lambda called"));

Il tipo di espressione lambda

Un'espressione lambda, di per sé, non ha un tipo specifico. Se è vero che i tipi e il numero di parametri, insieme al tipo di un valore di ritorno può trasmettere alcune informazioni di tipo, tali informazioni vincoleranno solo i tipi a cui può essere assegnato. Lambda riceve un tipo quando viene assegnato a un tipo di interfaccia funzionale in uno dei seguenti modi:

  • Assegnazione diretta a un tipo funzionale, ad es. myPredicate = s -> s.isEmpty()
  • Passandolo come parametro che ha un tipo funzionale, ad esempio stream.filter(s -> s.isEmpty())
  • Restituirlo da una funzione che restituisce un tipo funzionale, ad esempio return s -> s.isEmpty()
  • Trasmetterlo a un tipo funzionale, ad es. (Predicate<String>) s -> s.isEmpty()

Fino a quando non viene effettuata alcuna assegnazione a un tipo funzionale, la lambda non ha un tipo definito. Per illustrare, considera l'espressione lambda o -> o.isEmpty() . La stessa espressione lambda può essere assegnata a molti tipi funzionali diversi:

Predicate<String> javaStringPred = o -> o.isEmpty();
Function<String, Boolean> javaFunc = o -> o.isEmpty();
Predicate<List> javaListPred = o -> o.isEmpty();
Consumer<String> javaStringConsumer = o -> o.isEmpty(); // return value is ignored!
com.google.common.base.Predicate<String> guavaPredicate = o -> o.isEmpty();

Ora che sono assegnati, gli esempi mostrati sono di tipi completamente diversi anche se le espressioni lambda erano uguali e non possono essere assegnate l'una all'altra.

Riferimenti al metodo

I riferimenti al metodo consentono metodi predefiniti statici o di istanza che aderiscono a un'interfaccia funzionale compatibile da passare come argomenti anziché un'espressione lambda anonima.

Supponiamo di avere un modello:

class Person {
    private final String name;
    private final String surname;

    public Person(String name, String surname){
        this.name = name;
        this.surname = surname;
    }

    public String getName(){ return name; }
    public String getSurname(){ return surname; }
}

List<Person> people = getSomePeople();

Riferimento al metodo di istanza (a un'istanza arbitraria)

people.stream().map(Person::getName)

Il lambda equivalente:

people.stream().map(person -> person.getName())

In questo esempio, viene passato un riferimento al metodo di istanza getName() di tipo Person . Poiché è noto che è del tipo di raccolta, verrà richiamato il metodo sull'istanza (noto in seguito).


Riferimento al metodo di istanza (a un'istanza specifica)

people.forEach(System.out::println);

Poiché System.out è un'istanza di PrintStream , un riferimento al metodo a questa specifica istanza viene passato come argomento.

Il lambda equivalente:

people.forEach(person -> System.out.println(person));

Riferimento al metodo statico

Anche per la trasformazione degli stream possiamo applicare riferimenti a metodi statici:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream().map(String::valueOf)

Questo esempio passa un riferimento al metodo statico valueOf() sul tipo String . Pertanto, l'oggetto istanza nella raccolta viene passato come argomento a valueOf() .

Il lambda equivalente:

 numbers.stream().map(num -> String.valueOf(num))

Riferimento a un costruttore

List<String> strings = Arrays.asList("1", "2", "3");
strings.stream().map(Integer::new)

Leggi Raccogli elementi di un flusso in una raccolta per vedere come raccogliere elementi da raccogliere.

Qui viene utilizzato il costruttore di argomento String singolo del tipo Integer , per costruire un intero dato la stringa fornita come argomento. In questo caso, finché la stringa rappresenta un numero, lo stream verrà mappato su Integers. Il lambda equivalente:

strings.stream().map(s -> new Integer(s));

Cheat-sheet

Metodo di riferimento Formato Codice Lambda equivalente
Metodo statico TypeName::method (args) -> TypeName.method(args)
Metodo non statico (su istanza * ) instance::method (args) -> instance.method(args)
Metodo non statico (nessuna istanza) TypeName::method (instance, args) -> instance.method(args)
Costruttore ** TypeName::new (args) -> new TypeName(args)
Costruttore di array TypeName[]::new (int size) -> new TypeName[size]

* instance può essere qualsiasi espressione che valuta un riferimento a un'istanza, ad esempio getInstance()::method , this::method

** Se TypeName è una classe interna non statica, il riferimento del costruttore è valido solo nell'ambito di un'istanza di classe esterna

Implementazione di più interfacce

A volte potresti voler avere un'espressione lambda che implementa più di un'interfaccia. Questo è utile soprattutto con le interfacce marker (come java.io.Serializable ) poiché non aggiungono metodi astratti.

Ad esempio, si desidera creare un TreeSet di TreeSet con un Comparator personalizzato, quindi serializzarlo e inviarlo tramite la rete. L'approccio banale:

TreeSet<Long> ts = new TreeSet<>((x, y) -> Long.compare(y, x));

non funziona poiché il lambda per il comparatore non implementa Serializable . È possibile risolvere questo problema utilizzando i tipi di intersezione e specificando esplicitamente che questo lambda deve essere serializzabile:

TreeSet<Long> ts = new TreeSet<>(
    (Comparator<Long> & Serializable) (x, y) -> Long.compare(y, x));

Se si utilizzano spesso tipi di intersezione (ad esempio, se si utilizza un framework come Apache Spark in cui quasi tutto deve essere serializzabile), è possibile creare interfacce vuote e utilizzarle invece nel codice:

public interface SerializableComparator extends Comparator<Long>, Serializable {}

public class CustomTreeSet {
  public CustomTreeSet(SerializableComparator comparator) {}
}

In questo modo hai la certezza che il comparatore passato sarà serializzabile.

Lambdas ed Execute-around Pattern

Esistono diversi buoni esempi di utilizzo di lambda come FunctionalInterface in scenari semplici. Un caso d'uso abbastanza comune che può essere migliorato da lambda è quello che viene chiamato il modello Execute-Around. In questo modello, hai un set di codice standard di setup / teardown che è necessario per più scenari che circondano il codice caso d'uso specifico. Alcuni esempi comuni di questo sono file io, database io, blocchi try / catch.

interface DataProcessor {
    void process( Connection connection ) throws SQLException;;
}

public void doProcessing( DataProcessor processor ) throws SQLException{
    try (Connection connection = DBUtil.getDatabaseConnection();) {
        processor.process(connection);
        connection.commit();
    } 
}

Quindi chiamare questo metodo con un lambda potrebbe essere simile a:

public static void updateMyDAO(MyVO vo) throws DatabaseException {
    doProcessing((Connection conn) -> MyDAO.update(conn, ObjectMapper.map(vo)));
}

Questo non è limitato alle operazioni di I / O. Può essere applicato a qualsiasi scenario in cui attività simili di impostazione / eliminazione siano applicabili con variazioni minori. Il principale vantaggio di questo Pattern è il riutilizzo del codice e l'applicazione di DRY (Do not Repeat Yourself).

Usando espressione lambda con la tua interfaccia funzionale

Lambdas ha lo scopo di fornire un codice di implementazione inline per le interfacce a singolo metodo e la capacità di passarle come abbiamo fatto con le variabili normali. Li chiamiamo interfaccia funzionale.

Ad esempio, scrivendo un Runnable in una classe anonima e iniziando una discussione assomiglia a:

//Old way
new Thread(
        new Runnable(){
            public void run(){
                System.out.println("run logic...");
            }
        }
).start();

//lambdas, from Java 8
new Thread(
        ()-> System.out.println("run logic...")
).start();

Ora, in linea con sopra, diciamo che hai qualche interfaccia personalizzata:

interface TwoArgInterface {
    int operate(int a, int b);
}

Come usi lambda per dare l'implementazione di questa interfaccia nel tuo codice? Come nell'esempio Runnable mostrato sopra. Vedi il programma del driver qui sotto:

public class CustomLambda {
    public static void main(String[] args) {

        TwoArgInterface plusOperation = (a, b) -> a + b;
        TwoArgInterface divideOperation = (a,b)->{
            if (b==0) throw new IllegalArgumentException("Divisor can not be 0");
            return a/b;
        };

        System.out.println("Plus operation of 3 and 5 is: " + plusOperation.operate(3, 5));
        System.out.println("Divide operation 50 by 25 is: " + divideOperation.operate(50, 25));

    }
}

`return` restituisce solo il lambda, non il metodo esterno

Il metodo di return ritorna solo dal lambda, non dal metodo esterno.

Fai attenzione che questo è diverso da Scala e Kotlin!

void threeTimes(IntConsumer r) {
  for (int i = 0; i < 3; i++) {
    r.accept(i);
  }
}

void demo() {
  threeTimes(i -> {
    System.out.println(i);
    return; // Return from lambda to threeTimes only!
  });
}

Questo può portare a un comportamento imprevisto durante il tentativo di scrivere propri costrutti del linguaggio, come in costrutti built come ad esempio for i cicli return comporta in modo diverso:

void demo2() {
  for (int i = 0; i < 3; i++) {
    System.out.println(i);
    return; // Return from 'demo2' entirely
  }
}

In Scala e Kotlin, demo e demo2 avrebbero entrambi stampato solo 0 . Ma questo non è più coerente. L'approccio Java è coerente con il refactoring e l'uso delle classi - il return nel codice nella parte superiore, e il codice sottostante si comporta allo stesso modo:

void demo3() {
  threeTimes(new MyIntConsumer());
}

class MyIntConsumer implements IntConsumer {
  public void accept(int i) {
    System.out.println(i);
    return;
  }
}

Pertanto, il return Java è più coerente con i metodi di classe e il refactoring, ma meno con i builtin for e while , rimangono speciali.

Per questo motivo, i seguenti due sono equivalenti in Java:

IntStream.range(1, 4)
    .map(x -> x * x)
    .forEach(System.out::println);
IntStream.range(1, 4)
    .map(x -> { return x * x; })
    .forEach(System.out::println);

Inoltre, l'utilizzo di try-with-resources è sicuro in Java:

class Resource implements AutoCloseable {
  public void close() { System.out.println("close()"); }
}

void executeAround(Consumer<Resource> f) {
  try (Resource r = new Resource()) {
    System.out.print("before ");
    f.accept(r);
    System.out.print("after ");
  }
}

void demo4() {
  executeAround(r -> {
    System.out.print("accept() ");
    return; // Does not return from demo4, but frees the resource.
  });
}

stamperà before accept() after close() . Nella semantica di Scala e Kotlin, il try-with-resources non sarebbe stato chiuso, ma sarebbe stampato solo before accept() .

Chiusure Java con espressioni lambda.

Una chiusura lambda viene creata quando un'espressione lambda fa riferimento alle variabili di un ambito che racchiude (globale o locale). Le regole per farlo sono le stesse di quelle per i metodi inline e le classi anonime.

Le variabili locali da un ambito che viene utilizzato all'interno di un lambda devono essere final . Con Java 8 (la prima versione che supporta lambda), non è necessario dichiararli final nel contesto esterno, ma devono essere trattati in questo modo. Per esempio:

int n = 0; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = n;
    // do something
};

Questo è legale finché il valore della variabile n non è cambiato. Se provi a cambiare la variabile, all'interno o all'esterno di lambda, otterrai il seguente errore di compilazione:

"Le variabili locali referenziate da un'espressione lambda devono essere definitive o effettivamente definitive ".

Per esempio:

int n = 0;
Runnable r = () -> { // Using lambda
    int i = n;
    // do something
};
n++; // Will generate an error.

Se è necessario utilizzare una variabile variabile all'interno di una lambda, l'approccio normale è quello di dichiarare una copia final della variabile e utilizzare la copia. Per esempio

int n = 0;
final int k = n; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = k;
    // do something
};
n++;      // Now will not generate an error
r.run();  // Will run with i = 0 because k was 0 when the lambda was created

Naturalmente, il corpo della lambda non vede le modifiche alla variabile originale.

Si noti che Java non supporta le chiusure reali. Un lambda Java non può essere creato in un modo che gli consenta di vedere i cambiamenti nell'ambiente in cui è stato istanziato. Se vuoi implementare una chiusura che osserva o apporta modifiche al suo ambiente, devi simularlo usando una classe regolare. Per esempio:

// Does not compile ...
public IntUnaryOperator createAccumulator() {
    int value = 0;
    IntUnaryOperator accumulate = (x) -> { value += x; return value; };
    return accumulate;
}

L'esempio sopra non verrà compilato per le ragioni discusse in precedenza. Possiamo aggirare l'errore di compilazione come segue:

// Compiles, but is incorrect ...
public class AccumulatorGenerator {
    private int value = 0;

    public IntUnaryOperator createAccumulator() {
        IntUnaryOperator accumulate = (x) -> { value += x; return value; };
        return accumulate;
    }
}

Il problema è che questo rompe il contratto di design per l'interfaccia IntUnaryOperator che afferma che le istanze dovrebbero essere funzionali e senza stato. Se tale chiusura viene passata a funzioni integrate che accettano oggetti funzionali, è probabile che causi arresti anomali o comportamenti errati. Le chiusure che incapsulano lo stato mutabile dovrebbero essere implementate come classi regolari. Per esempio.

// Correct ...
public class Accumulator {
   private int value = 0;

   public int accumulate(int x) {
      value += x;
      return value;
   }
}

Lambda - Esempio di listener

Ascoltatore di classe anonima

Prima di Java 8, è molto comune che una classe anonima venga utilizzata per gestire l'evento click di un JButton, come mostrato nel codice seguente. Questo esempio mostra come implementare un listener anonimo nell'ambito di btn.addActionListener .

JButton btn = new JButton("My Button");
btn.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button was pressed");
    }
});

Ascoltatore Lambda

Poiché l'interfaccia ActionListener definisce solo un metodo actionPerformed() , è un'interfaccia funzionale che significa che c'è un posto dove utilizzare le espressioni Lambda per sostituire il codice boilerplate. L'esempio sopra può essere riscritto usando espressioni Lambda come segue:

JButton btn = new JButton("My Button");
btn.addActionListener(e -> {
    System.out.println("Button was pressed");
});

Stile tradizionale in stile Lambda

Modo tradizionale

interface MathOperation{
    boolean unaryOperation(int num);
}

public class LambdaTry {
    public static void main(String[] args) {
        MathOperation isEven = new MathOperation() {
            @Override
            public boolean unaryOperation(int num) {
                return num%2 == 0;
            }
        };
        
        System.out.println(isEven.unaryOperation(25));
        System.out.println(isEven.unaryOperation(20));
    }
}

Stile Lambda

  1. Rimuovi il nome della classe e il corpo dell'interfaccia funzionale.
public class LambdaTry {
    public static void main(String[] args) {
        MathOperation isEven = (int num) -> {
            return num%2 == 0;
        };
        
        System.out.println(isEven.unaryOperation(25));
        System.out.println(isEven.unaryOperation(20));
    }
}
  1. Dichiarazione di tipo opzionale
MathOperation isEven = (num) -> {
    return num%2 == 0;
};
  1. Parentesi opzionale attorno al parametro, se si tratta di un parametro singolo
MathOperation isEven = num -> {
    return num%2 == 0;
};
  1. Parentesi graffe facoltative, se c'è una sola linea nel corpo della funzione
  2. Parola chiave di restituzione facoltativa, se esiste una sola riga nel corpo della funzione
MathOperation isEven = num -> num%2 == 0;

Lambda e utilizzo della memoria

Dal momento che i lambda Java sono chiusure, possono "catturare" i valori delle variabili nell'accluso ambito lessicale. Mentre non tutti i lambda catturano qualcosa - i lambda semplici come s -> s.length() non catturano nulla e sono chiamati apolidi - i lambda di cattura richiedono un oggetto temporaneo per contenere le variabili catturate. In questo snippet di codice, lambda () -> j è un lambda di cattura e può causare l'allocazione di un oggetto quando viene valutato:

public static void main(String[] args) throws Exception {
    for (int i = 0; i < 1000000000; i++) {
        int j = i;
        doSomethingWithLambda(() -> j);
    }
}

Anche se potrebbe non essere immediatamente evidente dal momento che la new parola chiave non appare in nessun punto dello snippet, questo codice è suscettibile di creare 1.000.000.000 di oggetti separati per rappresentare le istanze dell'espressione () -> j lambda. Tuttavia, va anche notato che le versioni future di Java 1 potrebbero essere in grado di ottimizzarlo in modo che durante il runtime le istanze lambda venissero riutilizzate o rappresentate in altro modo.


1 - Ad esempio, Java 9 introduce una fase facoltativa di "collegamento" alla sequenza di build Java che fornirà l'opportunità di eseguire ottimizzazioni globali come questa.

Usando espressioni lambda e predicati per ottenere un determinato valore (s) da un elenco

A partire da Java 8, è possibile utilizzare espressioni lambda e predicati.

Esempio: utilizzare espressioni lambda e un predicato per ottenere un determinato valore da un elenco. In questo esempio ogni persona verrà stampata con il fatto se ha 18 anni o meno.

Classe di appartenenza:

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() { return age; }
    public String getName() { return name; }
}

L'interfaccia integrata Predicato dai pacchetti java.util.function.Predicate è un'interfaccia funzionale con un metodo di boolean test(T t) .

Esempio di utilizzo:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class LambdaExample {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Jeroen", 20));
        personList.add(new Person("Jack", 5));
        personList.add(new Person("Lisa", 19));

        print(personList, p -> p.getAge() >= 18);
    }

    private static void print(List<Person> personList, Predicate<Person> checker) {
        for (Person person : personList) {
            if (checker.test(person)) {
                System.out.print(person + " matches your expression.");
            } else {
                System.out.println(person  + " doesn't match your expression.");
            }
        }
    }
}

La print(personList, p -> p.getAge() >= 18); il metodo utilizza un'espressione lambda (poiché il predicato viene utilizzato come parametro) in cui è possibile definire l'espressione necessaria. Il metodo di verifica del correttore verifica se questa espressione è corretta o meno: checker.test(person) .

Puoi facilmente cambiarlo in qualcos'altro, ad esempio per print(personList, p -> p.getName().startsWith("J")); . Questo controllerà se il nome della persona inizia con una "J".



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow