JavaRush /Java-Blog /Random-DE /Lambda-Ausdrücke mit Beispielen

Lambda-Ausdrücke mit Beispielen

Veröffentlicht in der Gruppe Random-DE
Java ist zunächst eine vollständig objektorientierte Sprache. Mit Ausnahme primitiver Typen ist in Java alles ein Objekt. Auch Arrays sind Objekte. Instanzen jeder Klasse sind Objekte. Es gibt keine einzige Möglichkeit, eine Funktion separat (außerhalb einer Klasse – ca. übersetzt ) ​​zu definieren. Und es gibt keine Möglichkeit, eine Methode als Argument zu übergeben oder einen Methodenkörper als Ergebnis einer anderen Methode zurückzugeben. Es ist wie es ist. Aber das war vor Java 8. Lambda-Ausdrücke mit Beispielen - 1Seit den Tagen des guten alten Swing war es notwendig, anonyme Klassen zu schreiben, wenn es notwendig war, einer Methode Funktionalität zu übergeben. So sah beispielsweise das Hinzufügen eines Event-Handlers aus:
someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

                //Event listener implementation goes here...

            }
        });
Hier möchten wir dem Maus-Ereignis-Listener Code hinzufügen. Wir haben eine anonyme Klasse definiert MouseAdapterund daraus sofort ein Objekt erstellt. Auf diese Weise haben wir zusätzliche Funktionalität in die addMouseListener. Kurz gesagt, es ist nicht einfach, eine einfache Methode (Funktionalität) in Java durch Argumente zu übergeben. Diese Einschränkung zwang die Java 8-Entwickler dazu, der Sprachspezifikation eine Funktion wie Lambda-Ausdrücke hinzuzufügen.

Warum benötigt Java Lambda-Ausdrücke?

Von Anfang an hat sich die Java-Sprache kaum weiterentwickelt, abgesehen von Dingen wie Annotationen, Generics usw. Erstens ist Java immer objektorientiert geblieben. Nachdem man mit funktionalen Sprachen wie JavaScript gearbeitet hat, kann man verstehen, dass Java streng objektorientiert und stark typisiert ist. Funktionen werden in Java nicht benötigt. Für sich genommen sind sie in der Java-Welt nicht zu finden. In funktionalen Programmiersprachen stehen Funktionen im Vordergrund. Sie existieren für sich. Sie können sie Variablen zuweisen und über Argumente an andere Funktionen übergeben. JavaScript ist eines der besten Beispiele für funktionale Programmiersprachen. Im Internet finden Sie gute Artikel, die die Vorteile von JavaScript als funktionale Sprache detailliert beschreiben. Funktionale Sprachen verfügen über leistungsstarke Tools wie Closure, die gegenüber herkömmlichen Methoden zum Schreiben von Anwendungen eine Reihe von Vorteilen bieten. Ein Abschluss ist eine Funktion, an die eine Umgebung angehängt ist – eine Tabelle, die Verweise auf alle nicht lokalen Variablen der Funktion speichert. In Java können Schließungen durch Lambda-Ausdrücke simuliert werden. Natürlich gibt es Unterschiede zwischen Abschlüssen und Lambda-Ausdrücken, und zwar keine kleinen, aber Lambda-Ausdrücke sind eine gute Alternative zu Abschlüssen. In seinem sarkastischen und witzigen Blog beschreibt Steve Yegge, wie die Welt von Java strikt an Substantive (Entitäten, Objekte – ca. übersetzt ) ​​gebunden ist. Wenn Sie seinen Blog noch nicht gelesen haben, empfehle ich ihn. Er beschreibt auf lustige und interessante Weise den genauen Grund, warum Lambda-Ausdrücke zu Java hinzugefügt wurden. Lambda-Ausdrücke bringen Funktionen nach Java, die so lange gefehlt haben. Lambda-Ausdrücke verleihen der Sprache genau wie Objekte Funktionalität. Obwohl dies nicht zu 100 % der Fall ist, können Sie sehen, dass Lambda-Ausdrücke zwar keine Abschlüsse sind, aber ähnliche Funktionen bieten. In einer funktionalen Sprache sind Lambda-Ausdrücke Funktionen; In Java werden Lambda-Ausdrücke jedoch durch Objekte dargestellt und müssen einem bestimmten Objekttyp zugeordnet sein, der als funktionale Schnittstelle bezeichnet wird. Als nächstes schauen wir uns an, was es ist. Der Artikel von Mario Fusco „Why we need Lambda Expression in Java“ beschreibt ausführlich, warum alle modernen Sprachen Schließungsfunktionen benötigen.

Einführung in Lambda-Ausdrücke

Lambda-Ausdrücke sind anonyme Funktionen (möglicherweise keine 100 % korrekte Definition für Java, aber sie bringt etwas Klarheit). Einfach ausgedrückt handelt es sich hierbei um eine Methode ohne Deklaration, d.h. ohne Zugriffsmodifikatoren, Rückgabewert und Name. Kurz gesagt, sie ermöglichen es Ihnen, eine Methode zu schreiben und sie sofort zu verwenden. Dies ist besonders nützlich bei einem einmaligen Methodenaufruf, weil reduziert die Zeit, die zum Deklarieren und Schreiben einer Methode benötigt wird, ohne dass eine Klasse erstellt werden muss. Lambda-Ausdrücke in Java haben normalerweise die folgende Syntax (аргументы) -> (тело). Zum Beispiel:
(арг1, арг2...) -> { тело }

(тип1 арг1, тип2 арг2...) -> { тело }
Nachfolgend finden Sie einige Beispiele für echte Lambda-Ausdrücke:
(int a, int b) -> {  return a + b; }

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

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

() -> 42

() -> { return 3.1415 };

Struktur von Lambda-Ausdrücken

Lassen Sie uns die Struktur von Lambda-Ausdrücken untersuchen:
  • Lambda-Ausdrücke können 0 oder mehr Eingabeparameter haben.
  • Der Typ der Parameter kann explizit angegeben oder aus dem Kontext ermittelt werden. Beispielsweise int akann ( ) so geschrieben werden ( a)
  • Parameter werden in Klammern eingeschlossen und durch Kommas getrennt. Zum Beispiel ( a, b) oder ( int a, int b) oder ( String a, int b, float c)
  • Wenn keine Parameter vorhanden sind, müssen Sie leere Klammern verwenden. Zum Beispiel() -> 42
  • Wenn nur ein Parameter vorhanden ist und der Typ nicht explizit angegeben wird, können die Klammern weggelassen werden. Beispiel:a -> return a*a
  • Der Hauptteil eines Lambda-Ausdrucks kann 0 oder mehr Ausdrücke enthalten.
  • Wenn der Hauptteil aus einer einzelnen Anweisung besteht, darf er nicht in geschweifte Klammern eingeschlossen werden und der Rückgabewert kann ohne das Schlüsselwort angegeben werden return.
  • Andernfalls sind die geschweiften Klammern erforderlich (Codeblock) und der Rückgabewert muss am Ende mit einem Schlüsselwort angegeben werden return(andernfalls lautet der Rückgabetyp void).

Was ist eine funktionale Schnittstelle?

In Java sind Marker-Schnittstellen Schnittstellen ohne Deklaration von Methoden oder Feldern. Mit anderen Worten: Token-Schnittstellen sind leere Schnittstellen. Ebenso sind funktionale Schnittstellen Schnittstellen, auf denen nur eine abstrakte Methode deklariert ist. java.lang.Runnableist ein Beispiel für eine funktionale Schnittstelle. Es deklariert nur eine Methode void run(). Es gibt auch eine Schnittstelle ActionListener– ebenfalls funktionsfähig. Bisher mussten wir anonyme Klassen verwenden, um Objekte zu erstellen, die eine funktionale Schnittstelle implementieren. Mit Lambda-Ausdrücken ist alles einfacher geworden. Jeder Lambda-Ausdruck kann implizit an eine funktionale Schnittstelle gebunden werden. Sie können beispielsweise eine Referenz auf Runnableeine Schnittstelle erstellen, wie im folgenden Beispiel gezeigt:
Runnable r = () -> System.out.println("hello world");
Diese Art der Konvertierung erfolgt immer implizit, wenn wir keine funktionale Schnittstelle angeben:
new Thread(
    () -> System.out.println("hello world")
).start();
Im obigen Beispiel erstellt der Compiler automatisch einen Lambda-Ausdruck als Implementierung Runnableder Schnittstelle aus dem Klassenkonstruktor Thread: public Thread(Runnable r) { }. Hier sind einige Beispiele für Lambda-Ausdrücke und entsprechende Funktionsschnittstellen:
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 };
@FunctionalInterfaceDie in Java 8 gemäß der Java Language Specification hinzugefügte Annotation prüft, ob die deklarierte Schnittstelle funktionsfähig ist. Darüber hinaus enthält Java 8 eine Reihe vorgefertigter funktionaler Schnittstellen zur Verwendung mit Lambda-Ausdrücken. @FunctionalInterfacelöst einen Kompilierungsfehler aus, wenn die deklarierte Schnittstelle nicht funktionsfähig ist. Das Folgende ist ein Beispiel für die Definition einer funktionalen Schnittstelle:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
Wie aus der Definition hervorgeht, kann eine funktionale Schnittstelle nur eine abstrakte Methode haben. Wenn Sie versuchen, eine weitere abstrakte Methode hinzuzufügen, erhalten Sie einen Kompilierungsfehler. Beispiel:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

    public void doSomeMoreWork();

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

    public void doSomeWork();

}
public class WorkerInterfaceTest {

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

    public static void main(String [] args) {

      // Aufruf der doSomeWork-Methode über eine anonyme Klasse
      // (klassisch)
      execute(new WorkerInterface() {
            @Override
            public void doSomeWork() {
               System.out.println(„Arbeiter über eine anonyme Klasse angerufen“);
            }
        });

      // Aufruf der doSomeWork-Methode über Lambda-Ausdrücke
      // (Java 8 neu)
      execute( () -> System.out.println(„Arbeiter über Lambda angerufen“) );
    }

}
Abschluss:
Worker вызван через анонимный класс
Worker вызван через Lambda
Hier haben wir unsere eigene funktionale Schnittstelle definiert und einen Lambda-Ausdruck verwendet. Die Methode execute()kann Lambda-Ausdrücke als Argument akzeptieren.

Beispiele für Lambda-Ausdrücke

Der beste Weg, Lambda-Ausdrücke zu verstehen, besteht darin, sich einige Beispiele anzusehen: Ein Stream Threadkann auf zwei Arten initialisiert werden:
// Alter Weg:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread");
    }
}).start();
// Neuer Weg:
new Thread(
    () -> System.out.println("Hello from thread")
).start();
Die Ereignisverwaltung in Java 8 kann auch über Lambda-Ausdrücke erfolgen. ActionListenerEs gibt zwei Möglichkeiten , einer UI-Komponente einen Ereignishandler hinzuzufügen :
// Alter Weg:
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println(„Knopf gedrückt. Altmodisch!“);
    }
});
// Neuer Weg:
button.addActionListener( (e) -> {
        System.out.println(„Knopf gedrückt. Lambda!“);
});
Ein einfaches Beispiel für die Anzeige aller Elemente eines bestimmten Arrays. Beachten Sie, dass es mehr als eine Möglichkeit gibt, einen Lambda-Ausdruck zu verwenden. Nachfolgend erstellen wir einen Lambda-Ausdruck auf die übliche Weise mithilfe der Pfeilsyntax und verwenden außerdem den Doppelpunkt-Operator (::), der in Java 8 eine reguläre Methode in einen Lambda-Ausdruck umwandelt:
// Alter Weg:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
    System.out.println(n);
}
// Neuer Weg:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// Neue Möglichkeit mit Doppelpunkt-Operator ::
list.forEach(System.out::println);
Im folgenden Beispiel verwenden wir eine funktionale Schnittstelle, Predicateum einen Test zu erstellen und Elemente zu drucken, die diesen Test bestehen. Auf diese Weise können Sie Logik in Lambda-Ausdrücke integrieren und darauf basierend Dinge tun.
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(„Gibt alle Zahlen aus:“);
        evaluate(list, (n)->true);

        System.out.print(„Gibt keine Zahl aus:“);
        evaluate(list, (n)->false);

        System.out.print(„Gerade Zahlen ausgeben:“);
        evaluate(list, (n)-> n%2 == 0 );

        System.out.print(„Ungerade Zahlen ausgeben:“);
        evaluate(list, (n)-> n%2 == 1 );

        System.out.print(„Ausgabezahlen größer als 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();
    }

}
Abschluss:
Выводит все числа: 1 2 3 4 5 6 7
Не выводит ни одного числа:
Вывод четных чисел: 2 4 6
Вывод нечетных чисел: 1 3 5 7
Вывод чисел больше 5: 6 7
Durch Basteln mit Lambda-Ausdrücken können Sie das Quadrat jedes Elements der Liste anzeigen. Beachten Sie, dass wir die Methode verwenden stream(), um eine reguläre Liste in einen Stream umzuwandeln. Java 8 bietet eine tolle Klasse Stream( java.util.stream.Stream). Es enthält unzählige nützliche Methoden, mit denen Sie Lambda-Ausdrücke verwenden können. Wir übergeben einen Lambda-Ausdruck x -> x*xan die Methode map(), die ihn auf alle Elemente im Stream anwendet. Danach forEachdrucken wir alle Elemente der Liste aus.
// Alter Weg:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
    int x = n * n;
    System.out.println(x);
}
// Neuer Weg:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
Bei einer gegebenen Liste müssen Sie die Summe der Quadrate aller Elemente der Liste drucken. Mit Lambda-Ausdrücken können Sie dies erreichen, indem Sie nur eine Codezeile schreiben. In diesem Beispiel wird die Faltungsmethode (Reduktion) verwendet reduce(). Wir verwenden eine Methode, map()um jedes Element zu quadrieren, und verwenden dann eine Methode, reduce()um alle Elemente zu einer einzigen Zahl zusammenzufassen.
// Alter Weg:
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);
// Neuer Weg:
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);

Der Unterschied zwischen Lambda-Ausdrücken und anonymen Klassen

Der Hauptunterschied besteht in der Verwendung des Schlüsselworts this. Bei anonymen Klassen thisbezeichnet das Schlüsselwort „ „ ein Objekt der anonymen Klasse, während „ „ in einem Lambda-Ausdruck thisein Objekt der Klasse bezeichnet, in der der Lambda-Ausdruck verwendet wird. Ein weiterer Unterschied besteht in der Art und Weise, wie sie zusammengestellt werden. Java kompiliert Lambda-Ausdrücke und wandelt sie in privateKlassenmethoden um. Dabei wird die in Java 7 eingeführte invokedynamic- Anweisung für die dynamische Methodenbindung verwendet. Tal Weiss hat in seinem Blog beschrieben, wie Java Lambda-Ausdrücke in Bytecode kompiliert

Abschluss

Mark Reinhold (Chefarchitekt von Oracle) bezeichnete Lambda-Ausdrücke als die bedeutendste Änderung im Programmiermodell, die jemals stattgefunden hat – sogar bedeutender als Generika. Er muss recht haben, denn... Sie geben Java-Programmierern die funktionalen Programmiersprachenfähigkeiten, auf die alle gewartet haben. Zusammen mit Innovationen wie virtuellen Erweiterungsmethoden ermöglichen Ihnen Lambda-Ausdrücke das Schreiben von sehr hochwertigem Code. Ich hoffe, dieser Artikel hat Ihnen einen Einblick unter die Haube von Java 8 gegeben. Viel Glück :)
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION