Verstehen Sie Java 8 Lambda-Ausdrücke

Der Lambda-Typ ist so.

Runnable runner = () -> { System.out.println("Hello Lambda!"); };

Sie haben möglicherweise Angst, wenn Sie nicht damit vertraut sind, aber es wird tatsächlich mit vorhandenen Java-Funktionen konfiguriert. Zerlegen wir hier die Lambda-Gleichung und verstehen sie von Anfang an.

Klicken Sie hier für die Abkürzung für Lambda-Ausdruck. [Java] Auslassungszusammenfassung des Lambda-Ausdrucks

1. Versuchen Sie, die Lambda-Gleichung zu zerlegen

Lambda-Ausdrücke verwenden einen Mechanismus, der als lokale Klassen und anonyme Klassen bezeichnet wird.

1-1. Lokale Klasse

Eine lokale Klasse ist ein Mechanismus, der verwendet werden kann, indem eine Klasse während der Verarbeitung einer Methode deklariert wird.

public static void main(String[] args) {

  class Local {
    public void sayHello() {
      System.out.println("Hello!");
    }
  }

  Local local = new Local();
  local.sayHello(); // Hello!
}

Sie können auch eine lokale Klasse definieren, die die Schnittstelle implementiert.

public static void main(String[] args) {

  class Local implements Runnable {
    @Override
    public void run() {
      System.out.println("Hello Lambda!");
    }
  }

  Runnable runner = new Local();
  runner.run(); // Hello Lambda!
}

Schauen wir uns als nächstes die anonyme Klasse an.

1-2. Anonyme Klasse

Anonyme Klasse ist ein Mechanismus, bei dem die Deklaration der lokalen Klasse, die die Schnittstelle implementiert, weggelassen wird. Hier ist ein Beispiel für eine anonyme Klasse, die die Runnable-Schnittstelle implementiert.

public static void main(String[] args) {

  Runnable runner = new Runnable() {
    @Override
    public void run() {
      System.out.println("Hello Lambda!");
    }
  };

  runner.run(); //Hello Lambda!
}

Es sieht so aus, als würden Sie eine Instanz der Rannable-Schnittstelle erstellen, aber Sie erstellen tatsächlich eine Instanz einer anonymen Klasse, die die Rannable-Schnittstelle implementiert. Schauen wir uns zum Schluss den Lambda-Ausdruck an.

1-3 Lambda-Typ

Der Lambda-Ausdruck wird erhalten, indem "new Runnable () {}" und "public void run" in der anonymen Klasse weggelassen werden.

public static void main(String[] args) {

  Runnable runner = () -> { System.out.println("Hello Lambda!"); };
  runner.run(); //Hello Lambda!
}

Das erste () stellt das Argument der Ausführungsmethode dar, und der Inhalt von-> {} ist der Implementierungsinhalt der Ausführungsmethode. Der Runner-Variablen wird eine Instanz einer anonymen Klasse zugewiesen, die Runnable implementiert. Mit anderen Worten, ein Lambda-Ausdruck ist ein Ausdruck, der eine Instanz erstellt, die die Schnittstelle </ b> implementiert.

Wenn ich "new Runnable () {}" weglasse, weiß ich übrigens nicht, welche Art von Instanz ich erstellen soll. Java verfügt über einen Mechanismus, mit dem automatisch auf den Typ der zuzuweisenden Variablen geschlossen werden kann. Dieser Mechanismus wird als Typinferenz </ b> bezeichnet.

Sie können eine Schnittstelle als Argument angeben, um eine Methode zu definieren, die Lambda-Ausdrücke akzeptieren kann.

public static void main(String[] args) {
    method(()->{System.out.println("Hello Lambda!");});
}
public static void method(Runnable r) {
    r.run();
}
// Hello Lambda!

Dies ist auch ein Typ, der aus dem Argumenttyp abgeleitet wird, und eine Instanz des Typs Runnable wird automatisch generiert.

Wenn Sie "public void run" weglassen, wissen Sie auch nicht, welche Methode für eine Schnittstelle mit mehreren definierten Methoden überschrieben werden soll. Daher können Lambda-Ausdrücke nur Schnittstellen mit einer abstrakten Methode </ b> verwenden.

Die Rannable-Schnittstelle allein kann nur Lambda-Ausdrücke ohne Argumente und ohne Rückgabewert erstellen. Wenn Sie es in einer anderen Form erstellen möchten, wurde eine funktionale Schnittstelle hinzugefügt. Verwenden Sie diese.

2. Funktionsschnittstelle

Unter dem Paket java.util.function von SE8 wurden viele neue Schnittstellen hinzugefügt. Diese werden als funktionale Schnittstellen bezeichnet. https://docs.oracle.com/javase/jp/8/docs/api/java/util/function/package-summary.html

Funktionale Schnittstellen sind eine Gruppe von Schnittstellen, die eine einzige Methode haben und in Lambda-Ausdrücken sehr bequem zu verwenden sind. Unter diesen werden wir die häufig verwendeten Funktionen, Verbraucher und Prädikate vorstellen.

2-1. Function<T, R> In Funktion \ <T, R> gibt T den Typ des Arguments der Methode an, und R gibt den Typ des Rückgabewerts an. Die Methode ist R Apply (T).

Function<Integer, String> asterisker = (i) -> { return "*"+ i; };
String result = asterisker.apply(10);
System.out.println(result); // *10

Es gibt auch eine Schnittstelle namens BiFunction, die zwei Argumente akzeptiert.

BiFunction<Integer, Integer, Integer> adder = (a, b) -> { return a + b; };
int result = adder.apply(1, 2);
System.out.println(result); // 3

2-2. Consumer<T> T in Consumer \ gibt den Typ des Methodenarguments an. Die Methode ist void accept (T).

Consumer<String> buyer = (goods) -> { System.out.println(goods + "ich kaufte"); };
buyer.accept("Reisbällchen"); // Reisbällchenを購入しました。

Ich werde es nicht vorstellen, aber es gibt auch eine BiConsumer-Schnittstelle mit zwei Argumenten und keinem Rückgabewert.

2-3. Predicate<T> T in Prädikat \ gibt den Typ des Methodenarguments an. Die Methode ist der Boolesche Test (T).

Predicate<String> checker = (s)-> { return s.equals("Java"); };
boolean result = checker.test("Java");
System.out.println(result); //true

3. Verwendung des Lambda-Ausdrucks

Warum wurde der Lambda-Stil überhaupt geboren? Das liegt daran, dass ich den Prozess dynamisch ändern wollte, indem ich den Prozess als Argument anstelle eines Werts übergab. Typische Beispiele sind die Methoden Collections.sort und Stream API.

3-1. Collections.sort(List<T>, Comparator<? super T>) Die Sortiermethode hängt vom Typ und der Situation des Objekts ab. Wenn Sie beispielsweise Zahlen sortieren, können diese in einfacher aufsteigender Reihenfolge oder in absoluter aufsteigender Reihenfolge vorliegen. Um beide Arten zu realisieren, ist es notwendig, den numerischen Vergleichsprozess dynamisch umzuschalten. Daher ist Collections.sort so konzipiert, dass es den Vergleichsprozess selbst empfängt.

int[] numbers = {-1, 2, 0, -3, 8};

List<Integer> numbersList = new ArrayList<>();

for(int n : numbers) {
  numbersList.add(n);
}

Collections.sort(numbersList,[Sortiermethode]);

Geben Sie unter [Sortiermethode] die Instanz an, die die Vergleichsmethode (s1, s2) der Comparer-Schnittstelle implementiert. Die Vergleichsmethode gibt einen int-Typ zurück und bestimmt die Größe wie folgt.

Rückgabewert Groß und klein
Größer als 0 s1 > s2
0 s1 = s2
Weniger als 0 s1 < s2

Lassen Sie uns tatsächlich mit dem Lambda-Ausdruck sortieren.

Collections.sort(numbersList, (a, b) -> { return a - b; });

for(Integer n : numbersList) {
  System.out.print(n + " ");
}
// -3 -1 0 2 8

Sie können die Sortiermethode ändern, indem Sie den Inhalt des Lambda-Ausdrucks ändern. Lass es uns in absteigender Reihenfolge tun.

Collections.sort(numbersList, (a, b) -> { return b - a; });

for(Integer n : numbersList) {
  System.out.print(n + " ");
}
// 8 2 0 -1 -3 

Versuchen wir es in der Reihenfolge der absoluten Werte.

Collections.sort(numbersList, (a, b) -> { return a*a - b*b; });

for(Integer n : numbersList) {
  System.out.print(n + " ");
}
// 0 -1 2 -3 8 

Natürlich funktioniert es auch dann, wenn Sie eine Instanz einer regulären Klasse übergeben, die Comparator implementiert. Durch die Verwendung des Lambda-Ausdrucks scheint jedoch der Prozess selbst übergeben zu werden, sodass es möglich wird, präziser und intuitiver zu schreiben.

3-2.StreamAPI Der Collection-Schnittstelle wurde eine Methode namens stream hinzugefügt. Die Stream-Methode gibt eine eigene Stream-Instanz zurück. Stream definiert viele nützliche Methoden, die eine funktionale Schnittstelle als Argument verwenden.

void forEach(Consumer<T>) Die forEach-Methode verwendet einen Consumer als Argument und wiederholt den Vorgang für die Anzahl der Elemente.

int[] numbers = {-1, 2, 0, -3, 8};

List<Integer> numbersList = new ArrayList<>();

for(int n : numbers) {
  numbersList.add(n);
}

numbersList.stream().forEach((i) -> { System.out.print(i + " "); });
// -1 2 0 -3 8 

Stream filter(Predicate<T>) Die Filtermethode verwendet Predicate als Argument und gibt einen Stream zurück, der diejenigen ausschließt, die die Bedingungen nicht erfüllen. Da es sich auch um einen zurückgegebenen Stream handelt, können Sie die ForEach-Methode unverändert aufrufen.

numbersList.stream().filter((i) -> { return i > 0; })
                    .forEach((i) -> { System.out.print(i + " "); });
                    // 2 8 

Da der Rückgabewert der Filtermethode vom Typ Stream ist, können Sie weiterhin forEach aufrufen. Daher werden Methoden wie Filter als Zwischenoperationen bezeichnet. Andererseits wird eine Methode wie forEach als Beendigungsoperation bezeichnet.

Stream map(Function<T, R>) Die Map-Methode verwendet eine Funktion als Argument und gibt das verarbeitete Ergebnis als Stream zurück. Es ist eine Zwischenoperation wie ein Filter.

numbersList.stream().filter((i) -> { return i >= 0; })
                    .map((i) -> { return "*" + i + "*"; })
                    .forEach((s) -> { System.out.print(s + " "); });
                    // *2* *0* *8* 

Stream sorted(Comparator<T>) Obwohl es sich um ein java.util-Paket handelt, bietet es auch eine sortierte Methode, die einen Komparator als Argument verwendet. Es ist eine Zwischenoperation wie ein Filter.

numbersList.stream().filter((i) -> { return i >= 0; })
                    .sorted((i1, i2) -> { return i1 - i2; })
                    .map((i) -> { return "*" + i + "*"; })
                    .forEach((s) -> { System.out.print(s + " "); });
                    // *0* *2* *8* 

das ist alles. Wenn es sich von dem Lambda-Ausdruck unterscheidet, den Sie gesehen haben, suchen Sie nach abgekürzten Mustern.

Recommended Posts