Suche…


Einführung

Lambda-Ausdrücke bieten eine klare und präzise Möglichkeit, eine Schnittstelle mit einer Methode mithilfe eines Ausdrucks zu implementieren. Mit ihnen können Sie die Menge an Code reduzieren, die Sie erstellen und verwalten müssen. Obwohl sie anonymen Klassen ähnlich sind, haben sie selbst keine Typinformationen. Typ Inferenz muss passieren.

Methodenreferenzen implementieren funktionale Schnittstellen mit vorhandenen Methoden anstelle von Ausdrücken. Sie gehören auch zur Lambda-Familie.

Syntax

  • () -> {return expression; } // Zero-Arity mit Funktionsrumpf, um einen Wert zurückzugeben.
  • () -> Ausdruck // Abkürzung für die obige Deklaration; Es gibt kein Semikolon für Ausdrücke.
  • () -> {function-body} // Nebeneffekt im Lambda-Ausdruck zum Ausführen von Operationen.
  • Parametername -> Ausdruck // Lambda-Ausdruck mit einer Arität. In Lambda-Ausdrücken mit nur einem Argument kann die Klammer entfernt werden.
  • (Typ parameterName, type secondParameterName, ...) -> Ausdruck // Lambda, der einen Ausdruck mit links aufgelisteten Parametern auswertet
  • (parameterName, secondParameterName, ...) -> Ausdruck // Abkürzung, die die Parametertypen für die Parameternamen entfernt. Kann nur in Kontexten verwendet werden, die vom Compiler abgeleitet werden können, wenn die angegebene Parameterlistengröße mit einer (und nur einer) Größe der erwarteten funktionalen Schnittstellen übereinstimmt.

Verwenden von Lambda-Ausdrücken zum Sortieren einer Sammlung

Listen sortieren

Vor Java 8 war es erforderlich, die java.util.Comparator Schnittstelle mit einer anonymen (oder benannten) Klasse zu implementieren, wenn eine Liste 1 sortiert wird:

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());
        }
    }
);

Beginnend mit Java 8 kann die anonyme Klasse durch einen Lambda-Ausdruck ersetzt werden. Beachten Sie, dass die Typen für die Parameter p1 und p2 können, da der Compiler sie automatisch ableitet:

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

Das Beispiel kann vereinfacht werden, indem Comparator.comparing und Methodenreferenzen verwendet werden, die mit dem Symbol :: (Doppelpunkt) ausgedrückt werden.

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

Ein statischer Import erlaubt es uns, dies genauer zu formulieren, aber es ist fraglich, ob dies die Lesbarkeit insgesamt verbessert:

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

Auf diese Weise aufgebaute Komparatoren können auch miteinander verkettet werden. Wenn Sie zum Beispiel Personen nach ihrem Vornamen vergleichen und Personen mit demselben Vornamen verwenden, kann die thenComparing Methode auch mit dem thenComparing verglichen werden:

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

1 - Beachten Sie, dass Collections.sort (...) nur für Sammlungen funktioniert, die Untertypen von List . Die Set und Collection APIs implizieren keine Anordnung der Elemente.

Karten sortieren

Sie können die Einträge einer HashMap auf ähnliche Weise nach Wert sortieren. (Beachten Sie, dass eine LinkedHashMap als Ziel verwendet werden muss. Die Schlüssel in einer normalen HashMap sind ungeordnet.)

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));

Einführung in Java-Lambdas

Funktionale Schnittstellen

Lambdas können nur an einer funktionalen Schnittstelle arbeiten, bei der es sich um eine Schnittstelle mit nur einer abstrakten Methode handelt. Funktionsschnittstellen können eine beliebige Anzahl von default oder static Methoden haben. (Aus diesem Grund werden sie manchmal als Single Abstract Method Interfaces oder SAM Interfaces bezeichnet).

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();
}

Bei der Deklaration einer funktionalen Schnittstelle kann die Annotation @FunctionalInterface hinzugefügt werden. Dies hat keine besonderen Auswirkungen, es wird jedoch ein Compiler-Fehler generiert, wenn diese Anmerkung auf eine nicht funktionale Schnittstelle angewendet wird, die als Erinnerung daran erinnert, dass die Schnittstelle nicht geändert werden sollte.

@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
}

Umgekehrt ist dies keine funktionale Schnittstelle, da es mehr als eine abstrakte Methode gibt:

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

Dies ist auch keine funktionale Schnittstelle, da es keine Methoden gibt:

interface BlankFoo2 { }

Beachten Sie folgendes. Angenommen, Sie haben

interface Parent { public int parentMethod(); }

und

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

Dann kann Child keine funktionale Schnittstelle sein, da es zwei angegebene Methoden hat.

Java 8 bietet auch eine Reihe von generischen Templates mit funktionalen Schnittstellen im Paket java.util.function . Die eingebaute Schnittstelle Predicate<T> umgibt eine einzelne Methode, die einen Wert vom Typ T boolean und einen boolean Wert zurückgibt.


Lambda-Ausdrücke

Die Grundstruktur eines Lambda-Ausdrucks lautet:

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

fi wird dann eine Singleton-Instanz einer Klasse enthalten, ähnlich einer anonymen Klasse, die FunctionalInterface implementiert und die Definition der einen Methode lautet { System.out.println("Hello"); } . Mit anderen Worten, das obige ist größtenteils äquivalent zu:

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

Das Lambda ist der anonymen Klasse nur "weitgehend gleichwertig", da in einem Lambda die Bedeutung von Ausdrücken wie this , super oder toString() auf die Klasse verweist, in der die Zuweisung stattfindet, nicht auf das neu erstellte Objekt.

Sie können den Namen der Methode nicht angeben, wenn Sie ein Lambda verwenden. Dies sollte jedoch nicht erforderlich sein, da eine funktionale Schnittstelle nur eine abstrakte Methode haben muss, also überschreibt Java diese.

In Fällen, in denen der Typ des Lambda nicht sicher ist (z. B. überladene Methoden), können Sie dem Lambda einen Abguss hinzufügen, um dem Compiler mitzuteilen, welcher Typ es sein sollte:

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

Wenn für die einzelne Methode der funktionalen Schnittstelle Parameter erforderlich sind, sollten die lokalen formalen Namen zwischen den Klammern des Lambda stehen. Es ist nicht erforderlich, den Typ des Parameters zu deklarieren oder zurückzugeben, da diese von der Schnittstelle abgerufen werden (obwohl es kein Fehler ist, die Parametertypen zu deklarieren, wenn Sie möchten). Somit sind diese beiden Beispiele gleichwertig:

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

Die Klammern um das Argument können weggelassen werden, wenn die Funktion nur ein Argument hat:

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

Implizite Rückgaben

Wenn der in einem Lambda platzierte Code eher ein Java- Ausdruck als eine Anweisung ist , wird er als Methode behandelt, die den Wert des Ausdrucks zurückgibt. Somit sind die folgenden zwei äquivalent:

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

Zugriff auf lokale Variablen (Werteverschlüsse)

Da Lambdas eine syntaktische Abkürzung für anonyme Klassen sind, gelten dieselben Regeln für den Zugriff auf lokale Variablen im umschließenden Bereich. Die Variablen müssen als final behandelt und nicht innerhalb des Lambda geändert werden.

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
}

Wenn eine sich ändernde Variable auf diese Weise umbrochen werden soll, sollte ein reguläres Objekt verwendet werden, das eine Kopie der Variablen enthält. Lesen Sie mehr in Java Closures mit Lambda-Ausdrücken.


Lambdas akzeptieren

Da ein Lambda eine Implementierung einer Schnittstelle ist, muss nichts Besonderes getan werden, damit eine Methode ein Lambda akzeptiert: Jede Funktion, die eine funktionale Schnittstelle übernimmt, kann auch ein Lambda akzeptieren.

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

Der Typ eines Lambda-Ausdrucks

Ein Lambda-Ausdruck an sich hat keinen bestimmten Typ. Es ist zwar richtig, dass die Typen und die Anzahl der Parameter zusammen mit dem Typ eines Rückgabewerts einige Typinformationen übermitteln können. Diese Informationen beschränken jedoch nur, welchen Typen sie zugewiesen werden können. Das Lambda erhält einen Typ, wenn es auf eine der folgenden Arten einem funktionalen Schnittstellentyp zugewiesen wird:

  • Direkte Zuordnung zu einem funktionalen Typ, zB myPredicate = s -> s.isEmpty()
  • stream.filter(s -> s.isEmpty()) als Parameter mit einem funktionalen Typ, z. B. stream.filter(s -> s.isEmpty())
  • Rückgabe von einer Funktion, die einen Funktionstyp zurückgibt, z. B. return s -> s.isEmpty()
  • Casting in einen funktionalen Typ, z. B. (Predicate<String>) s -> s.isEmpty()

Bis zu einer solchen Zuordnung zu einem funktionalen Typ hat das Lambda keinen bestimmten Typ. Betrachten Sie zur Veranschaulichung den Lambda-Ausdruck o -> o.isEmpty() . Derselbe Lambda-Ausdruck kann vielen verschiedenen Funktionstypen zugewiesen werden:

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();

Jetzt, da sie zugewiesen sind, sind die Beispiele völlig unterschiedlich, auch wenn die Lambda-Ausdrücke gleich aussehen und sie nicht einander zugeordnet werden können.

Methodenreferenzen

Über Methodenreferenzen können vordefinierte statische oder Instanzmethoden, die einer kompatiblen funktionalen Schnittstelle entsprechen, als Argumente anstelle eines anonymen Lambda-Ausdrucks übergeben werden.

Angenommen, wir haben ein Modell:

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();

Instanzmethodenreferenz (auf eine beliebige Instanz)

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

Das Äquivalent Lambda:

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

In diesem Beispiel wird eine Methodenreferenz auf die Instanzmethode getName() vom Typ Person übergeben. Da bekannt ist, dass es vom Collection-Typ ist, wird die Methode der Instanz (später bekannt) aufgerufen.


Instanzmethodenreferenz (auf eine bestimmte Instanz)

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

Da System.out eine Instanz von PrintStream , wird eine Methodenreferenz auf diese bestimmte Instanz als Argument übergeben.

Das Äquivalent Lambda:

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

Statische Methodenreferenz

Auch für die Transformation von Streams können Verweise auf statische Methoden angewendet werden:

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

In diesem Beispiel wird ein Verweis auf die statische valueOf() -Methode für den String Typ übergeben. Daher wird das valueOf() in der Auflistung als Argument an valueOf() .

Das Äquivalent Lambda:

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

Verweis auf einen Konstruktor

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

Lesen Sie Sammeln von Elementen eines Streams in einer Sammlung, um zu erfahren, wie Elemente zur Sammlung gesammelt werden.

Der einzelne String-Argumentkonstruktor vom Typ Integer wird hier verwendet, um eine Ganzzahl zu erstellen, die die als Argument angegebene Zeichenfolge enthält. In diesem Fall wird der Stream auf Integer-Werte abgebildet, solange der String eine Zahl darstellt. Das Äquivalent Lambda:

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

Spickzettel

Methodenreferenzformat Code Äquivalentes Lambda
Statische Methode TypeName::method (args) -> TypeName.method(args)
Nicht statische Methode (in Instanz * ) instance::method (args) -> instance.method(args)
Nicht statische Methode (keine Instanz) TypeName::method (instance, args) -> instance.method(args)
Konstrukteur ** TypeName::new (args) -> new TypeName(args)
Array-Konstruktor TypeName[]::new (int size) -> new TypeName[size]

* instance kann ein beliebiger Ausdruck sein, der eine Referenz auf eine Instanz auswertet, z. B. die getInstance()::method , this::method

** Wenn TypeName eine nicht statische innere Klasse ist, ist die Konstruktorreferenz nur innerhalb des Bereichs einer äußeren Klasseninstanz gültig

Implementierung mehrerer Schnittstellen

Manchmal möchten Sie vielleicht einen Lambda-Ausdruck, der mehr als eine Schnittstelle implementiert. Dies ist vor allem bei Markerschnittstellen (z. B. java.io.Serializable ) hilfreich, da sie keine abstrakten Methoden hinzufügen.

Sie möchten beispielsweise ein TreeSet mit einem benutzerdefinierten Comparator erstellen, es serialisieren und über das Netzwerk senden. Der triviale Ansatz:

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

funktioniert nicht, da das Lambda für den Komparator kein Serializable implementiert. Sie können dies beheben, indem Sie Schnitttypen verwenden und explizit angeben, dass dieses Lambda serialisierbar sein muss:

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

Wenn Sie häufig Schnitttypen verwenden (wenn Sie beispielsweise ein Framework wie Apache Spark verwenden, bei dem fast alles serialisierbar sein muss), können Sie leere Schnittstellen erstellen und diese stattdessen in Ihrem Code verwenden:

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

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

Auf diese Weise wird garantiert, dass der übergebene Komparator serialisierbar ist.

Lambdas und Execute-around-Pattern

Es gibt mehrere gute Beispiele für die Verwendung von Lambdas als FunctionalInterface in einfachen Szenarien. Ein recht häufiger Anwendungsfall, der durch Lambdas verbessert werden kann, ist das sogenannte Execute-Around-Muster. In diesem Muster verfügen Sie über einen Satz von Standard-Setup- / Teardown-Code, der für mehrere Szenarien benötigt wird, die Anwendungsfall-spezifischen Code betreffen. Ein paar häufige Beispiele hierfür sind file io, database io, try / catch-Blöcke.

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();
    } 
}

Wenn Sie diese Methode dann mit einem Lambda aufrufen, könnte es so aussehen:

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

Dies ist nicht auf E / A-Vorgänge beschränkt. Sie kann auf jedes Szenario angewendet werden, in dem ähnliche Auf- / Abbauaufgaben mit geringfügigen Abweichungen anwendbar sind. Der Hauptvorteil dieses Musters ist die Wiederverwendung von Code und das Durchsetzen von DRY (Don't Repeat Yourself).

Verwenden des Lambda-Ausdrucks mit Ihrer eigenen funktionalen Schnittstelle

Lambdas sollen Inline-Implementierungscode für Schnittstellen mit einer einzigen Methode bereitstellen und die Möglichkeit geben, sie wie normale Variablen weiterzugeben. Wir nennen sie funktionale Schnittstelle.

Das Schreiben einer ausführbaren Klasse in anonyme Klasse und das Starten eines Threads sieht beispielsweise folgendermaßen aus:

//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();

Nun, im Einklang mit dem obigen Beispiel, können Sie sagen, Sie haben eine benutzerdefinierte Schnittstelle:

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

Wie verwenden Sie Lambda, um diese Schnittstelle in Ihrem Code zu implementieren? Dasselbe wie das oben gezeigte ausführbare Beispiel. Siehe das Treiberprogramm unten:

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" kehrt nur vom Lambda zurück, nicht von der äußeren Methode

Die return kehrt nur vom Lambda zurück, nicht von der äußeren Methode.

Beachten Sie, dass diese von Scala und Kotlin anders!

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!
  });
}

Dies kann zu unerwartetem Verhalten führen , wenn versucht eigene Sprachkonstrukte zu schreiben, wie es in gebautet Konstrukte wie for Schleifen return verhält sich anders:

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

In Scala und Kotlin würden demo und demo2 nur 0 drucken. Dies ist jedoch nicht konsistenter. Der Java-Ansatz ist konsistent mit dem Refactoring und der Verwendung von Klassen - die return im Code oben und der Code unten verhält sich gleich:

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

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

Daher ist die Java return mehr im Einklang mit Klassenmethoden und Refactoring, aber weniger mit dem for und while builtins, bleiben diese besondere.

Aus diesem Grund sind die folgenden zwei in Java gleichwertig:

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);

Darüber hinaus ist die Verwendung von Try-with-Ressourcen in Java sicher:

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.
  });
}

wird before accept() after close() gedruckt. In der Scala- und Kotlin-Semantik werden die Try-with-Ressourcen nicht geschlossen, sondern nur before accept() gedruckt.

Java-Closures mit Lambda-Ausdrücken.

Ein Lambda-Abschluss wird erstellt, wenn ein Lambda-Ausdruck auf die Variablen eines umschließenden Bereichs (global oder lokal) verweist. Die Regeln dafür sind die gleichen wie für Inline-Methoden und anonyme Klassen.

Lokale Variablen aus einem einschließenden Bereich, die in einem Lambda verwendet werden, müssen final . Mit Java 8 (der frühesten Version, die Lambdas unterstützt) müssen sie im äußeren Kontext nicht als final deklariert werden , müssen jedoch so behandelt werden. Zum Beispiel:

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

Dies ist zulässig, solange der Wert der Variablen n nicht geändert wird. Wenn Sie versuchen, die Variable innerhalb oder außerhalb des Lambda zu ändern, wird der folgende Kompilierungsfehler angezeigt:

msgstr "Lokale Variablen, auf die von einem Lambda - Ausdruck verwiesen wird, müssen final oder effektiv final sein. "

Zum Beispiel:

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

Wenn innerhalb eines Lambda eine sich ändernde Variable verwendet werden muss, wird normalerweise eine final Kopie der Variablen deklariert und die Kopie verwendet. Zum Beispiel

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

Natürlich sieht der Körper des Lambda die Änderungen an der ursprünglichen Variablen nicht.

Beachten Sie, dass Java keine echten Schließungen unterstützt. Ein Java-Lambda kann nicht so erstellt werden, dass Änderungen in der Umgebung sichtbar werden, in der es instanziiert wurde. Wenn Sie einen Abschluss implementieren möchten, der die Umgebung beobachtet oder Änderungen daran vornimmt, sollten Sie ihn mit einer regulären Klasse simulieren. Zum Beispiel:

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

Das obige Beispiel wird aus den zuvor diskutierten Gründen nicht kompiliert. Wir können den Kompilierungsfehler wie folgt umgehen:

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

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

Das Problem ist, dass dadurch der Designvertrag für die IntUnaryOperator Schnittstelle IntUnaryOperator , der besagt, dass Instanzen funktionsfähig und zustandslos sein sollten. Wenn ein solcher Abschluss an integrierte Funktionen übergeben wird, die Funktionsobjekte akzeptieren, kann dies zu Abstürzen oder fehlerhaftem Verhalten führen. Verschlüsse, die den veränderbaren Zustand einschließen, sollten als reguläre Klassen implementiert werden. Zum Beispiel.

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

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

Lambda - Hörer Beispiel

Anonymer Klassenlistener

Vor Java 8 ist es sehr üblich, dass eine anonyme Klasse verwendet wird, um das Click-Ereignis einer JButton-Klasse zu behandeln, wie im folgenden Code dargestellt. Dieses Beispiel zeigt, wie Sie einen anonymen Listener im Rahmen von btn.addActionListener .

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

Lambda-Hörer

Da die ActionListener Schnittstelle nur eine Methode actionPerformed() , handelt es sich um eine funktionale Schnittstelle, actionPerformed() es gibt einen Platz, an dem Lambda-Ausdrücke verwendet werden können, um den Boilerplate-Code zu ersetzen. Das obige Beispiel kann wie folgt mit Lambda-Ausdrücken umgeschrieben werden:

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

Traditioneller Stil im Lambda-Stil

Traditioneller Weg

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));
    }
}

Lambda-Stil

  1. Entfernen Sie den Klassennamen und den funktionalen Schnittstellenkörper.
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. Optionale Typdeklaration
MathOperation isEven = (num) -> {
    return num%2 == 0;
};
  1. Optionale Klammer um Parameter, wenn es sich um einen einzelnen Parameter handelt
MathOperation isEven = num -> {
    return num%2 == 0;
};
  1. Optionale geschweifte Klammern, wenn der Funktionskörper nur eine Zeile enthält
  2. Optionales return-Schlüsselwort, wenn der Funktionskörper nur eine Zeile enthält
MathOperation isEven = num -> num%2 == 0;

Lambdas und Speicherauslastung

Da Java-Lambdas Schließungen sind, können sie die Werte von Variablen im umgebenden lexikalischen Bereich "erfassen". Obwohl nicht alle Lambdas alles erfassen - einfache lambdas wie s -> s.length() erfaßt nichts und sind staatenlos genannt - Erfassung lambda erfordert ein temporäres Objekt die erfassten Variablen zu halten. In diesem Code-Snippet ist das Lambda () -> j ein Fang-Lambda und kann dazu führen, dass ein Objekt zugewiesen wird, wenn es ausgewertet wird:

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

Obwohl es möglicherweise nicht sofort offensichtlich ist, da das new Schlüsselwort an keiner Stelle im Snippet angezeigt wird, kann dieser Code 1.000.000.000 separate Objekte erstellen, um die Instanzen des () -> j Lambda-Ausdrucks darzustellen. Es sollte jedoch auch beachtet werden, dass zukünftige Versionen von Java 1 dies möglicherweise so optimieren können, dass die Lambda-Instanzen zur Laufzeit erneut verwendet oder auf andere Weise dargestellt wurden.


1 - Zum Beispiel führt Java 9 eine optionale "Link" -Phase für die Java-Buildsequenz ein, die die Möglichkeit für globale Optimierungen wie diese bietet.

Verwenden von Lambda-Ausdrücken und Prädikaten, um bestimmte Werte aus einer Liste zu erhalten

Ab Java 8 können Sie Lambda-Ausdrücke und Prädikate verwenden.

Beispiel: Verwenden Sie einen Lambda-Ausdruck und ein Prädikat, um einen bestimmten Wert aus einer Liste zu erhalten. In diesem Beispiel wird jede Person mit der Tatsache ausgedruckt, ob sie 18 Jahre oder älter ist oder nicht.

Personenklasse:

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; }
}

Die integrierte Schnittstelle Predicate aus den Paketen java.util.function.Predicate ist eine funktionale Schnittstelle mit einer boolean test(T t) .

Verwendungsbeispiel:

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.");
            }
        }
    }
}

Der print(personList, p -> p.getAge() >= 18); Die Methode nimmt einen Lambda-Ausdruck (da das Prädikat ein Parameter ist), in dem Sie den benötigten Ausdruck definieren können. Die Testmethode des checker.test(person) überprüft, ob dieser Ausdruck korrekt ist oder nicht: checker.test(person) .

Sie können dies leicht in etwas anderes ändern, beispielsweise zum print(personList, p -> p.getName().startsWith("J")); . Dadurch wird geprüft, ob der Name der Person mit einem "J" beginnt.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow