Einführung
In diesem Artikel werden anhand von Beispielen Lambda-Ausdrücke in Java, ihre Verwendung mit Funktionsschnittstellen, parametrisierten Funktionsschnittstellen und Stream-APIs untersucht.
Lambda-Ausdrücke wurden in Java 8 hinzugefügt. Ihr Hauptzweck besteht darin, die Lesbarkeit zu verbessern und die Codemenge zu reduzieren.
Bevor wir jedoch zu Lambdas übergehen, müssen wir die funktionalen Schnittstellen verstehen.
Was ist eine funktionale Schnittstelle?
Wenn eine Schnittstelle in Java nur eine abstrakte Methode enthält, wird sie als funktional bezeichnet. Diese einzelne Methode bestimmt den Zweck der Schnittstelle.
Beispielsweise ist die Runnable-Schnittstelle aus dem Paket java.lang funktionsfähig, da sie nur eine run () -Methode enthält.
Beispiel 1: Deklarieren einer Funktionsschnittstelle in Java
import java.lang.FunctionalInterface;
@FunctionalInterface
public interface MyInterface{
//
double getValue();
}
Im obigen Beispiel verfügt die MyInterface-Schnittstelle nur über eine abstrakte Methode, getValue (). Diese Schnittstelle ist also funktionsfähig.
Hier haben wir Anmerkungen verwendetFunctionalInterfaceDies hilft dem Compiler zu verstehen, dass die Schnittstelle funktionsfähig ist. Daher ist nicht mehr als eine abstrakte Methode zulässig. Wir können es jedoch weglassen.
In Java 7 wurden funktionale Schnittstellen als Single Abstract Methods (SAM) behandelt. SAMs wurden normalerweise mit anonymen Klassen implementiert.
Beispiel 2: Implementierung von SAM mit anonymer Klasse in Java
public class FunctionInterfaceTest {
public static void main(String[] args) {
//
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(" Runnable.")
}
}).start();
}
}
Ausführungsergebnis:
Runnable.
In diesem Beispiel akzeptieren wir eine anonyme Klasse, um die Methode aufzurufen. Dies half beim Schreiben von Programmen mit weniger Codezeilen in Java 7. Die Syntax blieb jedoch recht komplex und umständlich.
Java 8 hat die Funktionen von SAM erweitert und noch einen Schritt weiter gegangen. Wie wir wissen, enthält die Funktionsschnittstelle nur eine Methode, daher müssen wir den Namen der Methode nicht angeben, wenn wir sie als Argument übergeben. Genau das erlauben uns Lambda-Ausdrücke.
Einführung in Lambda-Ausdrücke
Lambda-Ausdrücke sind im Wesentlichen eine anonyme Klasse oder Methode. Der Lambda-Ausdruck wird nicht alleine ausgeführt. Stattdessen wird es verwendet, um die in der Funktionsschnittstelle definierte Methode zu implementieren.
Wie schreibe ich einen Lambda-Ausdruck in Java?
In Java haben Lambda-Ausdrücke die folgende Syntax:
(parameter list) -> lambda body
Hier haben wir einen neuen Operator (->) verwendet - den Lambda-Operator. Vielleicht scheint die Syntax etwas schwierig zu sein. Nehmen wir ein paar Beispiele.
Nehmen wir an, wir haben eine Methode wie diese:
double getPiValue() {
return 3.1415;
}
Wir können es mit einem Lambda schreiben wie:
() -> 3.1415
Diese Methode hat keine Parameter. Daher enthält die linke Seite des Ausdrucks leere Klammern. Die rechte Seite ist der Körper des Lambda-Ausdrucks, der seine Wirkung definiert. In unserem Fall beträgt der Rückgabewert 3,1415.
Arten von Lambda-Ausdrücken
In Java kann der Körper eines Lambda von zwei Arten sein.
1. Einzeilig
() -> System.out.println("Lambdas are great");
2. Block (mehrzeilig)
() -> {
double pi = 3.1415;
return pi;
};
Dieser Typ ermöglicht es einem Lambda-Ausdruck, intern mehrere Operationen auszuführen. Diese Operationen müssen in geschweiften Klammern eingeschlossen sein, gefolgt von einem Semikolon.
Hinweis: Mehrzeilige Lambda-Ausdrücke müssen im Gegensatz zu einzeiligen immer eine return-Anweisung haben.
Beispiel 3: Lambda-Ausdruck
Schreiben wir ein Java-Programm, das Pi mit einem Lambda-Ausdruck zurückgibt.
Wie bereits erwähnt, wird ein Lambda-Ausdruck nicht automatisch ausgeführt. Es bildet vielmehr eine Implementierung einer abstrakten Methode, die in einer funktionalen Schnittstelle deklariert ist.
Und so müssen wir zuerst die funktionale Schnittstelle beschreiben.
import java.lang.FunctionalInterface;
//
@FunctionalInterface
interface MyInterface{
//
double getPiValue();
}
public class Main {
public static void main( String[] args ) {
// MyInterface
MyInterface ref;
// -
ref = () -> 3.1415;
System.out.println("Value of Pi = " + ref.getPiValue());
}
}
Ausführungsergebnis:
Value of Pi = 3.1415
Im obigen Beispiel:
- Wir haben eine funktionale Schnittstelle erstellt, MyInterface, die eine abstrakte Methode enthält, getPiValue ().
- Innerhalb der Hauptklasse haben wir einen Verweis auf MyInterface deklariert. Beachten Sie, dass wir eine Schnittstellenreferenz deklarieren können, aber kein Objekt daraus erstellen können.
// MyInterface ref = new myInterface(); // MyInterface ref;
- Dann haben wir dem Link einen Lambda-Ausdruck zugewiesen
ref = () -> 3.1415;
- Schließlich haben wir die Methode getPiValue () unter Verwendung der Schnittstellenreferenz aufgerufen.
System.out.println("Value of Pi = " + ref.getPiValue());
Lambda-Ausdrücke mit Parametern
Bis zu diesem Punkt haben wir Lambda-Ausdrücke ohne Parameter erstellt. Lambdas können jedoch genau wie Methoden Parameter haben.
(n) -> (n % 2) == 0
In diesem Beispiel ist die Variable n in Klammern der Parameter, der an den Lambda-Ausdruck übergeben wird. Der Körper des Lambda nimmt einen Parameter und prüft ihn auf Parität.
Beispiel 4: Verwenden eines Lambda-Ausdrucks mit Parametern
@FunctionalInterface
interface MyInterface {
//
String reverse(String n);
}
public class Main {
public static void main( String[] args ) {
// MyInterface
// -
MyInterface ref = (str) -> {
String result = "";
for (int i = str.length()-1; i >= 0 ; i--)
result += str.charAt(i);
return result;
};
//
System.out.println("Lambda reversed = " + ref.reverse("Lambda"));
}
}
Ausführungsergebnis:
Lambda reversed = adbmaL
Parametrierte Funktionsschnittstelle
Bis zu diesem Punkt haben wir funktionale Schnittstellen verwendet, die nur einen Werttyp akzeptieren. Zum Beispiel:
@FunctionalInterface
interface MyInterface {
String reverseString(String n);
}
Die obige Funktionsschnittstelle akzeptiert nur String und gibt String zurück. Wir können unsere Schnittstelle jedoch generisch gestalten, um sie mit jedem Datentyp zu verwenden.
Beispiel 5: Parametrisierte Schnittstelle und Lambda-Ausdrücke
//
@FunctionalInterface
interface GenericInterface<T> {
//
T func(T t);
}
public class Main {
public static void main( String[] args ) {
//
// String
//
GenericInterface<String> reverse = (str) -> {
String result = "";
for (int i = str.length()-1; i >= 0 ; i--)
result += str.charAt(i);
return result;
};
System.out.println("Lambda reversed = " + reverse.func("Lambda"));
//
// Integer
//
GenericInterface<Integer> factorial = (n) -> {
int result = 1;
for (int i = 1; i <= n; i++)
result = i * result;
return result;
};
System.out.println("factorial of 5 = " + factorial.func(5));
}
}
Ausführungsergebnis:
Lambda reversed = adbmaL
factorial of 5 = 120
In diesem Beispiel haben wir eine parametrisierte Funktionsschnittstelle GenericInterface erstellt, die eine parametrisierte func () -Methode enthält.
Dann in der Hauptklasse:
- GenericInterface <String> reverse - Erstellt einen Link zu einer Schnittstelle, die mit String funktioniert.
- GenericInterface <Integer> Fakultät - Erstellt einen Link zu einer Schnittstelle, die mit Integer arbeitet.
Lambda Expressions und Stream API
Das JDK8 fügt mit java.util.stream ein neues Paket hinzu, mit dem Java-Entwickler Vorgänge wie Suchen, Filtern, Abgleichen, Zusammenführen oder Bearbeiten von Sammlungen wie Listen ausführen können.
Zum Beispiel haben wir einen Datenstrom (in unserem Fall eine Liste von Zeichenfolgen), in dem jede Zeichenfolge den Namen eines Landes und seiner Stadt enthält. Jetzt können wir diesen Datenstrom verarbeiten und nur die Städte Nepals auswählen.
Dazu können wir eine Kombination aus Stream-API- und Lambda-Ausdrücken verwenden.
Beispiel 6: Verwenden von Lambdas in der Stream-API
import java.util.ArrayList;
import java.util.List;
public class StreamMain {
//
static List<String> places = new ArrayList<>();
//
public static List getPlaces(){
//
places.add("Nepal, Kathmandu");
places.add("Nepal, Pokhara");
places.add("India, Delhi");
places.add("USA, New York");
places.add("Africa, Nigeria");
return places;
}
public static void main( String[] args ) {
List<String> myPlaces = getPlaces();
System.out.println("Places from Nepal:");
//
myPlaces.stream()
.filter((p) -> p.startsWith("Nepal"))
.map((p) -> p.toUpperCase())
.sorted()
.forEach((p) -> System.out.println(p));
}
}
Ausführungsergebnis:
Places from Nepal:
NEPAL, KATHMANDU
NEPAL, POKHARA
Beachten Sie im obigen Beispiel diesen Ausdruck:
myPlaces.stream()
.filter((p) -> p.startsWith("Nepal"))
.map((p) -> p.toUpperCase())
.sorted()
.forEach((p) -> System.out.println(p));
Hier verwenden wir Methoden wie filter (), map (), forEach () aus der Stream-API, die Lambdas als Parameter verwenden können.
Wir können auch unsere eigenen Ausdrücke basierend auf der oben beschriebenen Syntax beschreiben. Dadurch können wir die Anzahl der Codezeilen reduzieren.