JavaRush /Java-Blog /Random-DE /Beliebt bei Lambda-Ausdrücken in Java. Mit Beispielen und...
Стас Пасинков
Level 26
Киев

Beliebt bei Lambda-Ausdrücken in Java. Mit Beispielen und Aufgaben. Teil 2

Veröffentlicht in der Gruppe Random-DE
Für wen ist dieser Artikel?
  • Für diejenigen, die den ersten Teil dieses Artikels gelesen haben;

  • Für diejenigen, die glauben, Java Core bereits gut zu kennen, aber keine Ahnung von Lambda-Ausdrücken in Java haben. Oder vielleicht haben Sie schon etwas über Lambdas gehört, aber ohne Details.

  • Für diejenigen, die ein gewisses Verständnis für Lambda-Ausdrücke haben, aber immer noch Angst davor haben, sie zu verwenden.

Zugriff auf externe Variablen

Wird dieser Code mit einer anonymen Klasse kompiliert?
int counter = 0;
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter++;
    }
};
Nein. Die Variable countermuss sein final. Oder nicht unbedingt final, aber auf jeden Fall kann es seinen Wert nicht ändern. Das gleiche Prinzip wird in Lambda-Ausdrücken verwendet. Sie haben Zugriff auf alle Variablen, die für sie von dem Ort aus, an dem sie deklariert sind, „sichtbar“ sind. Aber das Lambda sollte sie nicht ändern (einen neuen Wert zuweisen). Es gibt zwar eine Option, diese Einschränkung in anonymen Klassen zu umgehen. Es reicht aus, nur eine Variable eines Referenztyps zu erstellen und den internen Zustand des Objekts zu ändern. In diesem Fall zeigt die Variable selbst auf dasselbe Objekt, und in diesem Fall können Sie es sicher als angeben final.
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() {
    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
Hier ist unsere Variable countereine Referenz auf ein Objekt vom Typ AtomicInteger. Und um den Zustand dieses Objekts zu ändern, wird die Methode verwendet incrementAndGet(). Der Wert der Variablen selbst ändert sich während der Programmausführung nicht und zeigt immer auf dasselbe Objekt, was es uns ermöglicht, eine Variable sofort mit dem Schlüsselwort zu deklarieren final. Die gleichen Beispiele, aber mit Lambda-Ausdrücken:
int counter = 0;
Runnable r = () -> counter++;
Es wird aus demselben Grund nicht kompiliert wie die Option mit einer anonymen Klasse: counterEs sollte sich nicht ändern, während das Programm ausgeführt wird. Aber so - alles ist gut:
final AtomicInteger counter = new AtomicInteger(0);
Runnable r = () -> counter.incrementAndGet();
Dies gilt auch für aufrufende Methoden. Von einem Lambda-Ausdruck aus können Sie nicht nur auf alle „sichtbaren“ Variablen zugreifen, sondern auch die Methoden aufrufen, auf die Sie Zugriff haben.
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> staticMethod();
        new Thread(runnable).start();
    }

    private static void staticMethod() {
        System.out.println("Я - метод staticMethod(), и меня только-что кто-то вызвал!");
    }
}
Obwohl die Methode staticMethod()privat ist, kann sie innerhalb der Methode aufgerufen werden main(), sodass sie auch aus dem Lambda heraus aufgerufen werden kann, das in der Methode erstellt wird main.

Der Zeitpunkt der Ausführung des Lambda-Ausdruckscodes

Diese Frage mag Ihnen zu einfach erscheinen, aber es lohnt sich zu fragen: Wann wird der Code im Lambda-Ausdruck ausgeführt? Im Moment der Schöpfung? Oder in dem Moment, in dem (noch unbekannt wo) es aufgerufen wird? Es ist ganz einfach zu überprüfen.
System.out.println("Запуск программы");

// много всякого разного Codeа
// ...

System.out.println("Перед объявлением лямбды");

Runnable runnable = () -> System.out.println("Я - лямбда!");

System.out.println("После объявления лямбды");

// много всякого другого Codeа
// ...

System.out.println("Перед передачей лямбды в тред");
new Thread(runnable).start();
Ausgabe auf dem Display:
Запуск программы
Перед объявлением лямбды
После объявления лямбды
Перед передачей лямбды в тред
Я - лямбда!
Es ist ersichtlich, dass der Lambda-Ausdruckscode ganz am Ende ausgeführt wurde, nachdem der Thread erstellt wurde, und erst, als der Programmausführungsprozess die tatsächliche Ausführung der Methode erreichte run(). Und zum Zeitpunkt der Ankündigung überhaupt nicht. Durch die Deklaration eines Lambda-Ausdrucks haben wir lediglich ein Objekt dieses Typs erstellt Runnableund das Verhalten seiner Methode beschrieben run(). Die Methode selbst wurde viel später eingeführt.

Methodenreferenzen?

Steht nicht in direktem Zusammenhang mit Lambdas selbst, aber ich denke, es wäre logisch, in diesem Artikel ein paar Worte darüber zu sagen. Nehmen wir an, wir haben einen Lambda-Ausdruck, der nichts Besonderes tut, sondern nur eine Methode aufruft.
x -> System.out.println(x)
Sie gaben ihm etwas х, und es rief ihn einfach an System.out.println()und reichte ihn dort weiter х. In diesem Fall können wir es durch einen Link zu der von uns benötigten Methode ersetzen. So:
System.out::println
Ja, ohne die Klammern am Ende! Vollständigeres Beispiel:
List<String> strings = new LinkedList<>();
strings.add("Mama");
strings.add("Seife");
strings.add("rahmen");

strings.forEach(x -> System.out.println(x));
In der letzten Zeile verwenden wir eine Methode forEach(), die ein Schnittstellenobjekt akzeptiert Consumer. Dies ist wiederum eine funktionale Schnittstelle mit nur einer Methode void accept(T t). Dementsprechend schreiben wir einen Lambda-Ausdruck, der einen Parameter annimmt (da er in der Schnittstelle selbst eingegeben wird, geben wir nicht den Typ des Parameters an, sondern geben an, dass er aufgerufen wird х). Im Hauptteil des Lambda-Ausdrucks schreiben wir den Code das wird ausgeführt, wenn die Methode aufgerufen wird accept(). Hier zeigen wir einfach auf dem Bildschirm an, was in der Variablen steht х. Die Methode selbst forEach()durchläuft alle Elemente der Sammlung, ruft Consumerdie Methode des an sie übergebenen Schnittstellenobjekts (unser Lambda) auf accept(). Dabei wird jedes Element aus der Sammlung übergeben. Wie ich bereits sagte, ist dies ein Lambda-Ausdruck (der einfach eine andere Methode aufruft), den wir durch einen Verweis auf die Methode ersetzen können, die wir benötigen. Dann sieht unser Code so aus:
List<String> strings = new LinkedList<>();
strings.add("Mama");
strings.add("Seife");
strings.add("rahmen");

strings.forEach(System.out::println);
Die Hauptsache ist, dass die akzeptierten Parameter der Methoden (println()und accept()). Da die Methode println()alles akzeptieren kann (sie ist für alle Grundelemente und alle Objekte überladen), können wir anstelle eines Lambda-Ausdrucks forEach()nur einen Verweis auf die Methode übergeben println(). Dann forEach()nimmt sie jedes Element der Sammlung und übergibt es direkt an die Methode println(). Für diejenigen, die zum ersten Mal darauf stoßen, beachten Sie bitte: Bitte beachten Sie, dass wir die Methode nicht aufrufen System.out.println()(mit Punkten zwischen Wörtern und mit Klammern am Ende), sondern die Referenz auf diese Methode selbst übergeben.
strings.forEach(System.out.println());
wir werden einen Kompilierungsfehler haben. Denn vor dem Aufruf forEach()sieht Java, dass es aufgerufen wird System.out.println(), versteht, dass es zurückgegeben wird voidund versucht , dies an das dort wartende Objekt des Typs voidzu übergeben . forEach()Consumer

Syntax für die Verwendung von Methodenreferenzen

Es ist ganz einfach:
  1. Übergabe eines Verweises auf eine statische MethodeNameКласса:: NameСтатическогоМетода?

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mama");
            strings.add("Seife");
            strings.add("rahmen");
    
            strings.forEach(Main::staticMethod);
        }
    
        private static void staticMethod(String s) {
            // do something
        }
    }
  2. Übergeben eines Verweises auf eine nicht statische Methode unter Verwendung eines vorhandenen ObjektsNameПеременнойСОбъектом:: Methodenname

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mama");
            strings.add("Seife");
            strings.add("rahmen");
    
            Main instance = new Main();
            strings.forEach(instance::nonStaticMethod);
        }
    
        private void nonStaticMethod(String s) {
            // do something
        }
    }
  3. Wir übergeben einen Verweis auf eine nicht statische Methode mithilfe der Klasse, in der eine solche Methode implementiert istNameКласса:: Methodenname

    public class Main {
        public static void main(String[] args) {
            List<User> users = new LinkedList<>();
            users.add(new User(„Wasja“));
            users.add(new User("Коля"));
            users.add(new User("Петя"));
    
            users.forEach(User::print);
        }
    
        private static class User {
            private String name;
    
            private User(String name) {
                this.name = name;
            }
    
            private void print() {
                System.out.println(name);
            }
        }
    }
  4. Einen Link an den Konstruktor übergeben Die NameКласса::new
    Verwendung von Methodenlinks ist sehr praktisch, wenn es eine fertige Methode gibt, mit der Sie vollkommen zufrieden sind, und Sie diese als Callback verwenden möchten. Anstatt in diesem Fall einen Lambda-Ausdruck mit dem Code dieser Methode oder einen Lambda-Ausdruck zu schreiben, bei dem wir diese Methode einfach aufrufen, übergeben wir einfach einen Verweis darauf. Und alle.

Interessanter Unterschied zwischen anonymer Klasse und Lambda-Ausdruck

In einer anonymen Klasse thisverweist das Schlüsselwort auf ein Objekt dieser anonymen Klasse. Und wenn wir es innerhalb eines Lambda verwenden this, erhalten wir Zugriff auf das Objekt der Framing-Klasse. Wo wir diesen Ausdruck tatsächlich geschrieben haben. Dies liegt daran, dass Lambda-Ausdrücke beim Kompilieren zu einer privaten Methode der Klasse werden, in der sie geschrieben werden. Ich würde die Verwendung dieser „Funktion“ nicht empfehlen, da sie einen Nebeneffekt hat, der den Prinzipien der funktionalen Programmierung widerspricht. Dieser Ansatz stimmt jedoch durchaus mit OOP überein. ;)

Woher habe ich die Informationen und was kann ich sonst noch lesen?

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION