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.
Seit 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) {
}
});
Hier möchten wir dem Maus-Ereignis-Listener Code hinzufügen. Wir haben eine anonyme Klasse definiert
MouseAdapter
und 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 a
kann ( ) 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.Runnable
ist 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
Runnable
eine 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
Runnable
der 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 };
@FunctionalInterface
Die 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.
@FunctionalInterface
lö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-выражений. Пример:
@FunctionalInterface
public interface WorkerInterface {
public void doSomeWork();
}
public class WorkerInterfaceTest {
public static void execute(WorkerInterface worker) {
worker.doSomeWork();
}
public static void main(String [] args) {
execute(new WorkerInterface() {
@Override
public void doSomeWork() {
System.out.println(„Arbeiter über eine anonyme Klasse angerufen“);
}
});
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
Thread
kann auf zwei Arten initialisiert werden:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello from thread");
}
}).start();
new Thread(
() -> System.out.println("Hello from thread")
).start();
Die Ereignisverwaltung in Java 8 kann auch über Lambda-Ausdrücke erfolgen.
ActionListener
Es gibt zwei Möglichkeiten , einer UI-Komponente einen Ereignishandler hinzuzufügen :
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println(„Knopf gedrückt. Altmodisch!“);
}
});
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:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
System.out.println(n);
}
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
list.forEach(System.out::println);
Im folgenden Beispiel verwenden wir eine funktionale Schnittstelle,
Predicate
um 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*x
an die Methode
map()
, die ihn auf alle Elemente im Stream anwendet. Danach
forEach
drucken wir alle Elemente der Liste aus.
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
int x = n * n;
System.out.println(x);
}
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.
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);
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
this
bezeichnet das Schlüsselwort „ „ ein Objekt der anonymen Klasse, während „ „ in einem Lambda-Ausdruck
this
ein 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
private
Klassenmethoden 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 :)
GO TO FULL VERSION