Übersetzungsanmerkung: Karte (Zuordnung) und Reduzierung (Reduzierung, Vereinfachung) sind zwei sehr grundlegende Konzepte in der Mathematik. Sie tauchten schon lange in verschiedenen funktionalen Programmiersprachen auf. Erst 2003 führte Google sie weiter wendete sie an: Nachdem paralleles Rechnen in verteilten Systemen implementiert wurde, begann der Name dieser Kombination in der Computerwelt zu glänzen (diese funktionalen Fans glauben das vielleicht nicht). In diesem Artikel werden wir das Debüt von Map- und Reduce-Kombinationen sehen, nachdem Java 8 die funktionale Programmierung unterstützt (dies ist nur eine vorläufige Einführung, es werden später spezielle Themen dazu folgen).
Reduzieren Sie einen Satz
Bisher haben wir mehrere neue Techniken zum Betreiben von Sammlungen eingeführt: Finden passender Elemente, Finden einzelner Elemente und Sammlungstransformationen. Eines haben diese Operationen gemeinsam: Sie wirken sich alle auf ein einzelnes Element in der Sammlung aus. Es besteht keine Notwendigkeit, Elemente zu vergleichen oder Operationen an zwei Elementen durchzuführen. In diesem Abschnitt sehen wir uns an, wie man Elemente vergleicht und ein Operationsergebnis während des Sammlungsdurchlaufs dynamisch verwaltet.
Beginnen wir mit einfachen Beispielen und arbeiten uns dann nach oben. Im ersten Beispiel durchlaufen wir zunächst die Freundessammlung und berechnen die Gesamtzahl der Zeichen in allen Namen.
Kopieren Sie den Codecode wie folgt:
System.out.println("Gesamtzahl der Zeichen in allen Namen: " + friends.stream()
.mapToInt(name -> name.length())
.Summe());
Um die Gesamtzahl der Zeichen zu berechnen, müssen wir die Länge jedes Namens kennen. Dies kann einfach über die Methode mapToInt() erreicht werden. Nachdem wir die Namen in entsprechende Längen umgewandelt haben, müssen wir sie am Ende nur noch zusammenzählen. Um dies zu erreichen, haben wir eine integrierte sum()-Methode. Hier ist die endgültige Ausgabe:
Kopieren Sie den Codecode wie folgt:
Gesamtzahl der Zeichen in allen Namen: 26
Wir haben eine Variante der Kartenoperation verwendet, die Methode „mapToInt()“ (z. B. „mapToInt“, „mapToDouble“ usw.), die bestimmte Stream-Typen wie „IntStream“ oder „DoubleStream“ generiert, und dann die Gesamtzahl der Zeichen basierend auf berechnet zurückgegebene Länge.
Zusätzlich zur Verwendung der Summenmethode gibt es viele ähnliche Methoden, die verwendet werden können, z. B. max() zum Ermitteln der maximalen Länge, min() zum Ermitteln der minimalen Länge, sorted() zum Sortieren der Längen und Average() bis Finden Sie die durchschnittliche Länge usw. Warten Sie.
Ein weiterer attraktiver Aspekt des obigen Beispiels ist der immer beliebter werdende MapReduce-Modus. Die Methode „map()“ führt die Zuordnung durch, und die Methode „sum()“ ist eine häufig verwendete Reduzierungsoperation. Tatsächlich verwendet die Implementierung der sum()-Methode im JDK die Reduce()-Methode. Werfen wir einen Blick auf einige der am häufigsten verwendeten Formen von Reduzierungsoperationen.
Beispielsweise durchlaufen wir alle Namen und geben den Namen mit dem längsten Namen aus. Bei mehreren längsten Namen geben wir den zuerst gefundenen aus. Eine Möglichkeit besteht darin, dass wir die maximale Länge berechnen und dann das erste Element auswählen, das dieser Länge entspricht. Allerdings muss dazu die Liste zweimal durchlaufen werden – was zu ineffizient ist. Hier kommt die Reduzierungsoperation ins Spiel.
Wir können die Reduzierungsoperation verwenden, um die Längen zweier Elemente zu vergleichen, dann das längste zurückzugeben und es weiter mit den übrigen Elementen zu vergleichen. Wie andere Funktionen höherer Ordnung, die wir zuvor gesehen haben, durchläuft auch die Methode „reduce()“ die gesamte Sammlung. Es zeichnet unter anderem das vom Lambda-Ausdruck zurückgegebene Berechnungsergebnis auf. Wenn es ein Beispiel gibt, das uns helfen kann, dies besser zu verstehen, schauen wir uns zunächst einen Code an.
Kopieren Sie den Codecode wie folgt:
final Optional<String> aLongName = friends.stream()
.reduce((name1, name2) ->
name1.length() >= name2.length() ? name1 : name2);
aLongName.ifPresent(name ->
System.out.println(String.format("Ein längster Name: %s", name)));
Der an die Methode „reduc()“ übergebene Lambda-Ausdruck empfängt zwei Parameter, name1 und name2, vergleicht deren Längen und gibt den längsten zurück. Die Methode „reduce()“ hat keine Ahnung, was wir tun werden. Diese Logik wird in den von uns übergebenen Lambda-Ausdruck reduziert – dies ist eine vereinfachte Implementierung des Strategiemusters.
Dieser Lambda-Ausdruck kann an die Apply-Methode der Funktionsschnittstelle eines BinaryOperator im JDK angepasst werden. Dies ist genau die Art von Argument, die die Reduce-Methode akzeptiert. Lassen Sie uns diese Reduzierungsmethode ausführen und prüfen, ob sie den ersten der beiden längsten Namen korrekt auswählen kann.
Kopieren Sie den Codecode wie folgt:
Ein längster Name: Brian
Wenn die Methode „reduce()“ die Sammlung durchläuft, ruft sie zunächst den Lambda-Ausdruck für die ersten beiden Elemente der Sammlung auf und das vom Aufruf zurückgegebene Ergebnis wird weiterhin für den nächsten Aufruf verwendet. Beim zweiten Aufruf wird der Wert von name1 an das Ergebnis des vorherigen Aufrufs gebunden und der Wert von name2 ist das dritte Element der Sammlung. In dieser Reihenfolge werden auch die restlichen Elemente aufgerufen. Das Ergebnis des letzten Lambda-Ausdrucksaufrufs ist das Ergebnis, das von der gesamten Methode Reduce() zurückgegeben wird.
Die Methode „reduce()“ gibt einen optionalen Wert zurück, da die an sie übergebene Sammlung möglicherweise leer ist. In diesem Fall gäbe es keinen längsten Namen. Wenn die Liste nur ein Element enthält, gibt die Reduce-Methode dieses Element direkt zurück und ruft den Lambda-Ausdruck nicht auf.
Aus diesem Beispiel können wir schließen, dass das Ergebnis von Reduzieren höchstens ein Element in der Menge sein kann. Wenn wir einen Standard- oder Basiswert zurückgeben möchten, können wir eine Variante der Methode Reduce() verwenden, die einen zusätzlichen Parameter akzeptiert. Wenn der kürzeste Name beispielsweise Steve ist, können wir ihn wie folgt an die Methode Reduce() übergeben:
Kopieren Sie den Codecode wie folgt:
letzter String steveOrLonger = friends.stream()
.reduce("Steve", (name1, name2) ->
name1.length() >= name2.length() ? name1 : name2);
Wenn ein Name länger ist, wird dieser Name ausgewählt; andernfalls wird der Basiswert Steve zurückgegeben. Diese Version der Methode „reduce()“ gibt kein optionales Objekt zurück, da bei leerer Sammlung ein Standardwert zurückgegeben wird, unabhängig davon, ob kein Rückgabewert vorhanden ist.
Bevor wir dieses Kapitel beenden, werfen wir einen Blick auf eine sehr einfache, aber nicht so einfache Operation bei Mengenoperationen: das Zusammenführen von Elementen.
Elemente zusammenführen
Wir haben gelernt, wie man Elemente findet, Sammlungen durchläuft und konvertiert. Es gibt jedoch noch eine weitere gängige Operation – das Zusammenfügen von Sammlungselementen – ohne diese neu hinzugefügte Funktion „join()“ wäre der zuvor erwähnte prägnante und elegante Code vergeblich. Diese einfache Methode ist so praktisch, dass sie zu einer der am häufigsten verwendeten Funktionen im JDK geworden ist. Sehen wir uns an, wie man damit Elemente in einer Liste durch Kommas getrennt ausdruckt.
Wir verwenden diese Freundesliste immer noch. Wenn Sie die alte Methode in der JDK-Bibliothek verwenden, was sollten Sie tun, wenn Sie alle durch Kommas getrennten Namen ausdrucken möchten?
Wir müssen die Liste durchlaufen und die Elemente einzeln ausdrucken. Die for-Schleife in Java 5 wurde gegenüber der vorherigen verbessert, also verwenden wir sie.
Kopieren Sie den Codecode wie folgt:
for(String name : friends) {
System.out.print(name + ", ");
}
System.out.println();
Der Code ist sehr einfach, mal sehen, was seine Ausgabe ist.
Kopieren Sie den Codecode wie folgt:
Brian, Nate, Neal, Raju, Sara, Scott,
Verdammt, da ist dieses nervige Komma am Ende (können wir Scott am Ende die Schuld geben?). Wie kann ich Java anweisen, hier kein Komma zu setzen? Leider wird die Schleife Schritt für Schritt ausgeführt und es ist nicht einfach, am Ende etwas Besonderes zu tun. Um dieses Problem zu lösen, können wir die ursprüngliche Schleifenmethode verwenden.
Kopieren Sie den Codecode wie folgt:
for(int i = 0; i < friends.size() - 1; i++) {
System.out.print(friends.get(i) + ", ");
}
if(friends.size() > 0)
System.out.println(friends.get(friends.size() - 1));
Mal sehen, ob die Ausgabe dieser Version in Ordnung ist.
Kopieren Sie den Codecode wie folgt:
Brian, Nate, Neal, Raju, Sara, Scott
Das Ergebnis ist immer noch gut, aber dieser Code ist nicht schmeichelhaft. Rette uns, Java.
Wir müssen diesen Schmerz nicht mehr ertragen. Die StringJoiner-Klasse in Java 8 hilft uns nicht nur, diese Probleme zu lösen, sondern die String-Klasse fügt auch eine Join-Methode hinzu, sodass wir die oben genannten Dinge durch eine Codezeile ersetzen können.
Kopieren Sie den Codecode wie folgt:
System.out.println(String.join(", ", friends));
Schauen Sie sich das an, die Ergebnisse sind genauso zufriedenstellend wie der Code.
Kopieren Sie den Codecode wie folgt:
Brian, Nate, Neal, Raju, Sara, Scott
Das Ergebnis ist immer noch gut, aber dieser Code ist nicht schmeichelhaft. Rette uns, Java.
Wir müssen diesen Schmerz nicht mehr ertragen. Die StringJoiner-Klasse in Java 8 hilft uns nicht nur, diese Probleme zu lösen, sondern die String-Klasse fügt auch eine Join-Methode hinzu, sodass wir die oben genannten Dinge durch eine Codezeile ersetzen können.
Kopieren Sie den Codecode wie folgt:
System.out.println(String.join(", ", friends));
Schauen Sie sich das an, die Ergebnisse sind genauso zufriedenstellend wie der Code.
Kopieren Sie den Codecode wie folgt:
Brian, Nate, Neal, Raju, Sara, Scott
In der zugrunde liegenden Implementierung ruft die Methode String.join() die Klasse StringJoiner auf, um den als zweiten Parameter übergebenen Wert (bei dem es sich um einen Parameter variabler Länge handelt) in eine lange Zeichenfolge zusammenzufügen, wobei der erste Parameter als Trennzeichen verwendet wird. Natürlich ist diese Methode mehr als nur das Zusammenfügen von Kommas. Dank dieser neu hinzugefügten Methoden und Klassen können wir beispielsweise eine Reihe von Pfaden übergeben und ganz einfach einen Klassenpfad formulieren.
Wir wissen bereits, wie man Listenelemente verbindet. Vor dem Verbinden von Listen können wir die Elemente natürlich auch transformieren. Als nächstes können wir auch die Methode filter() verwenden, um die gewünschten Elemente herauszufiltern. Der letzte Schritt des Verbindens der Listenelemente mithilfe von Kommas oder anderen Trennzeichen ist lediglich eine einfache Reduzierungsoperation.
Wir können die Methode „reduce()“ verwenden, um die Elemente zu einem String zu verketten, aber das erfordert etwas Arbeit unsererseits. JDK verfügt über eine sehr praktische Collect()-Methode, die auch eine Variante von Reduce() ist. Wir können sie verwenden, um Elemente zu einem gewünschten Wert zu kombinieren.
Die Methode „collect()“ führt den Reduktionsvorgang aus, delegiert den spezifischen Vorgang jedoch zur Ausführung an einen Kollektor. Wir können die konvertierten Elemente zu einer ArrayList zusammenführen. Wenn wir mit dem vorherigen Beispiel fortfahren, können wir die konvertierten Elemente zu einer durch Kommas getrennten Zeichenfolge verketten.
Kopieren Sie den Codecode wie folgt:
System.out.println(
friends.stream()
.map(String::toUpperCase)
.collect(joining(", ")));
Wir haben die Methode „collect()“ für die konvertierte Liste aufgerufen und ihr einen von der Methode „joining()“ zurückgegebenen Collector übergeben. „Joining“ ist eine statische Methode in der Toolklasse „Collectors“. Der Collector ist wie ein Empfänger. Er empfängt die von Collect übergebenen Objekte und speichert sie im gewünschten Format: ArrayList, String usw. Wir werden diese Methode in der Collect-Methode und der Collectors-Klasse auf Seite 52 näher untersuchen.
Dies ist der Ausgabename, jetzt werden sie in Großbuchstaben geschrieben und durch Kommas getrennt.
Kopieren Sie den Codecode wie folgt:
BRIAN, NATE, NEAL, RAJU, SARA, SCOTT
Zusammenfassen
Sammlungen sind in der Programmierung sehr verbreitet. Mit Lambda-Ausdrücken sind die Sammlungsoperationen von Java einfacher und einfacher geworden. Der gesamte umständliche alte Code für Erfassungsvorgänge kann durch diesen eleganten und prägnanten neuen Ansatz ersetzt werden. Der interne Iterator macht das Durchlaufen und Umwandeln von Sammlungen bequemer, ohne die Probleme der Variabilität, und das Auffinden von Sammlungselementen wird extrem einfach. Mit diesen neuen Methoden können Sie viel weniger Code schreiben. Dadurch ist der Code einfacher zu warten, konzentriert sich stärker auf die Geschäftslogik und erfordert weniger grundlegende Programmiervorgänge.
Im nächsten Kapitel werden wir sehen, wie Lambda-Ausdrücke eine weitere grundlegende Operation in der Programmentwicklung vereinfachen: String-Manipulation und Objektvergleich.