[Java11] Stream-Zusammenfassung - Vorteile von Stream -

Was ist Stream?

Zuordnungen für Sammlungsklassen, die funktionale Operationen für einen Strom von Elementen unterstützen, z. B. Transformationen reduzieren. Quelle: Offizielles Dokument

Lassen Sie uns zunächst einmal sehen

Teilweise geändert von Official Reference

stream.java


final int sum = widgets.stream()
                       .filter(w -> w.getColor() == RED)
                       .mapToInt(w -> w.getWeight())
                       .sum();

Im obigen Beispiel wird "Widgets" aus "Widgets" mit "Farbe" "ROT" extrahiert, um den Gesamtwert von "Gewicht" zu berechnen. Wenn Sie es als Anweisung eingeben, lautet es wie folgt.

zur Aussage.java


int sum = 0;
for (Widget w: widgets) {
    if (w.getColor() == RED) {
        sum += w.getWeight();
    }
}

Was kann ich tun?

Wie ich am Anfang schrieb, ist Stream "* eine Klasse, die funktionale Operationen für einen Stream von Elementen unterstützt, z. B. die Konvertierung von Kartenreduzierungen für Sammlungen. *"

Grob gesagt ist es das, was ich mit der for-Anweisung gemacht habe. Sie können den oben genannten Gesamtwert berechnen, den Maximalwert ermitteln, eine neue Liste erstellen usw.

Viele der Stream-Klassenmethoden verwenden eine funktionale Schnittstelle als Argument. Übergeben Sie beispielsweise ** das Filtern ** an die Filterfunktion und ** das Konvertieren in den Integer-Typ ** an die mapToInt-Funktion. Stream verfügt über viele Methoden, die einen neuen Stream zurückgeben, und Sie können die Verarbeitung zur flexiblen Verarbeitung an diesen übergeben.

Was ist gut?

Ich denke, es gibt zwei Hauptvorteile:

  1. Verbesserte Lesbarkeit
  2. Leistungsverbesserung durch Parallelverarbeitung

Verbesserte Lesbarkeit

Dies liegt daran, dass die Schreibmethode mit Strean im Vergleich zu normalem Code ** "deklarativ" ** ist. Achten Sie besonders auf den Teil des obigen Beispiels, in dem die Summe ("Summe") berechnet wird.

Die for-Anweisung heißt im Vergleich zum Stream ** "instruktiv" **. Durch "deklaratives" Schreiben können Sie kleine Implementierungen ausblenden und die Codemenge reduzieren. Es ist nur eine Erklärung, was zu tun ist, sodass Sie anhand des Codes leicht verstehen können, was Sie tun möchten. Andererseits ist es umso schwieriger zu verstehen, je komplizierter der Prozess ist und je mehr Fehler verursacht werden.

Erfahrungsgemäß ist es einfacher, Code zu lesen und sicherer zu machen, wenn eine Methode weniger wichtigen Code enthält, unabhängig davon, ob Stream verwendet wird oder nicht.

Leistungsverbesserung durch Parallelverarbeitung

Es gibt viele Fälle, in denen dieser Vorteil nicht erzielt werden kann. (Erfahrung) Dies liegt daran, dass die Ausführung in Serie ** schneller ** ist, es sei denn, Sie haben es mit einer sehr großen Datenmenge zu tun.

Glücklicherweise müssen Sie nur "parallel ()" aufrufen, um die Ausführung von Strean in eine parallele Verarbeitung umzuwandeln. Sie können die Vorteile der Parallelverarbeitung maximieren, indem Sie ** die Datenmenge ** und ** das Problem berücksichtigen, die Reihenfolge nicht beizubehalten **, anstatt die Parallelverarbeitung in den dunklen Wolken durchzuführen.

Die folgenden Artikel sind hilfreich. Referenz: Grundkenntnisse der Funktionsschnittstelle und der Stream-API, die ihren wahren Charakter im Lambda-Ausdruck (3/3) zeigen

Wie schreibst du

Es gibt ** 3 Schritte **, um einen Prozess mit Stream zu schreiben.

  1. Erstellen Sie einen Stream
  2. Zwischenbetrieb
  3. Kündigungsoperation

Ein Beispiel ist wie folgt.

stream.java


final int sum = widgets.stream() //Stream erstellen
                       .filter(w -> w.getColor() == RED) //Zwischenbetrieb
                       .mapToInt(w -> w.getWeight()) //Zwischenbetrieb
                       .sum(); //Kündigungsoperation

Erstellen Sie einen Stream

Ich denke, dass es oft aus einer Sammlung wie List oder Set erstellt wird. Genau das ist "widgets.stream ()". Darüber hinaus gibt es Methoden wie das Erstellen eines Streams aus mehreren Werten und die Verwendung von Stream.Builder.

Zwischenbetrieb

filter`` mapToInt ist die Zwischenoperation. Es konvertiert den Wert jedes Elements und extrahiert das Element basierend auf der Bedingung. Gibt einen Stream wie "Stream " oder "IntStream" zurück. Eine Methodenkette ist möglich, weil sie einen Stream zurückgibt. Die Verarbeitung der Zwischenoperation ist nicht der Zeitpunkt, zu dem "Fileter" usw. aufgerufen wird. Es wird zum ersten Mal ausgeführt, wenn die Beendigungsoperation ausgeführt wird. Dies ist eine Zwischenoperation

Beispiel für die tatsächliche Kenntnis des Ausführungszeitpunkts der Zwischenoperation
public class Main {
    public static void main(String[] args) throws Exception {
        List<Widget> widgets = List.of(new Widget(RED, 10), new Widget(BLUE, 20));
        
        Stream<Widget> stream1 = widgets.stream();
        Stream<Widget> stream2 = stream1.filter(w -> w.getColor() == RED);
        System.out.println("complete filtering");
        IntStream stream3 = stream2.mapToInt(w -> w.getWeight());
        System.out.println("complete mappint to integer");
        final int sum = stream3.sum();
    }
}

class Widget {
    private Color color;
    private int weight;
    
    public Widget(Color color, int weight) {
        this.color = color;
        this.weight = weight;
    }
    public Color getColor() {
        System.out.println(color);
        return color;
    }
    public int getWeight() {
        System.out.println(weight);
        return weight;
    }
}
complete filtering
complete mappint to integer
java.awt.Color[r=255,g=0,b=0]
10
java.awt.Color[r=0,g=0,b=255]

Es ist ersichtlich, dass nicht jeder Prozess unmittelbar nach der Zwischenoperation ausgeführt wird.

Kündigungsoperation

sum ist die Beendigungsoperation. Andere schließen collect`` findFirst ein. Gibt den Gesamtwert oder eine neue Sammlung zurück. Im Gegensatz zur Zwischenoperation variiert der zurückgegebene Wert. Es gibt viele Dinge, die Sie tun können. "Gesamtwert berechnen", "Neue Sammlung erstellen", "Für jedes Element (für jedes Element) verarbeiten" usw.

Ich denke, es ist besser, ein Beispiel dafür zu zeigen, was für Zwischenoperationen und Terminierungsoperationen getan werden kann, daher möchte ich ab dem nächsten Mal detailliert zusammenfassen.

Funktionsschnittstelle

Ich habe es bisher ignoriert, aber ich möchte die Existenz von w-> w.getColor () == RED`` w-> w.getWeight () erwähnen. Diese Grammatik heißt ** Lambda-Ausdruck **, aber Sie müssen nicht unbedingt einen Lambda-Ausdruck schreiben. ** Lambda-Ausdrücke sind nur eine Möglichkeit, eine funktionale Schnittstelle zu implementieren. ** ** **

Dieses Verständnis ist für das Verständnis von Stream unvermeidlich, da die meisten Methoden in Stream eine funktionale Schnittstelle als Argument verwenden.

Um ehrlich zu sein, ist es in Ordnung, zunächst in einer Atmosphäre zu schreiben. Selbst wenn Sie von nun an das Schlimmste nicht verstehen können, ist es möglicherweise in Ordnung, einen Stream zu schreiben.

Was ist eine funktionale Schnittstelle?

Eine Art Schnittstelle. Diese Schnittstelle hat ** nur eine Methode ** Was in Java standardmäßig bereitgestellt wird , Sie können auch Ihre eigenen erstellen.

Um jeden Typ leicht verständlich zu machen, ist das erste Beispiel so weit wie möglich unterteilt.

Stream<Widget> stream1 = widgets.stream();
        
Predicate<Widget> predicate = w -> w.getColor() == RED;
Stream<Widget> stream2 = stream1.filter(predicate);
        
ToIntFunction<Widget> toIntFunction = w -> w.getWeight();
IntStream stream3 = stream2.mapToInt(toIntFunction);
        
final int sum = stream3.sum();

Das Predicate`` ToIntFunction, dem der Lambda-Ausdruck zugewiesen ist, ist die funktionale Schnittstelle. Die Definition von "ToIntFunction" lautet wie folgt.

java:java.util.function.ToIntFunction.java


@FunctionalInterface
public interface ToIntFunction<T> {
    int applyAsInt​(T value);
}

@ FunctionalInterface wird zur Funktionsschnittstelle hinzugefügt, es gibt jedoch kein Funktionsproblem, auch wenn es nicht hinzugefügt wird.

Wie zu implementieren

Sie müssen eine Instanz erstellen, die eine funktionale Schnittstelle implementiert, um sie an eine Stream-Methode zu übergeben. Es gibt jedoch drei Hauptmethoden, um sie zu implementieren.

  • Anonyme Klasse
  • Lambda-Stil
  • Methodenreferenz

ist.

Vergleichen wir jede Implementierungsmethode. Als Voraussetzung gehen wir davon aus, dass Sie eine solche Widget-Klasse haben.

Widget.java


class Widget {
    private Color color;
    private int weight;
    
    public Widget(Color color, int weight) {
        this.color = color;
        this.weight = weight;
    }
    
    public Color getColor() {
        return color;
    }
    
    public boolean isColorRed() {
        return color == RED;
    }
    
    public int getWeight() {
        return weight;
    }
}

Anonyme Klasse

Es ist nicht sehr leicht zu lesen.

Anonyme Klasse.java


final int sum = widgets.stream()
                .filter(new Predicate<Widget>() {
                    public boolean test(Widget w) {
                        return w.isColorRed();
                    }
                }) 
                .mapToInt(new ToIntFunction<Widget>() {
                    public int applyAsInt(Widget w) {
                        return w.getWeight();
                    }
                }) 
                .sum();
Sie können dies immer noch tun ...

Das funktioniert natürlich immer noch.

static class WidgetTestColorIsRed implements Predicate<Widget> {
    public boolean test(Widget w) {
        return w.isColorRed();
    }
}
static class WidgetToWeightFunction implements ToIntFunction<Widget> {
    public int applyAsInt(Widget w) {
        return w.getWeight();
    }
}
final int sum = widgets.stream()
                .filter(new WidgetTestColorIsRed())
                .mapToInt(new WidgetToWeightFunction())
                .sum();

Ich glaube nicht, dass es viele Fälle gibt, in denen man einfach schreiben kann.

Lambda-Stil

Es ist überwiegend leichter zu lesen.

Lambda-Stil.java


final int sum = widgets.stream()
                .filter(w -> w.isColorRed()) 
                .mapToInt(w -> w.getWeight())
                .sum(); 

Es gibt verschiedene Möglichkeiten, einen Lambda-Stil zu schreiben.

//Keine Argumente
() -> "Konstante";

//1 Argument
n -> n + 1; //Klammern können weggelassen werden
(n) -> 2 * n;

//2 oder mehr Argumente
(a, b) -> Math.sqrt(a * a + b * b);
(x, y, z) -> x * y * z;

//Mehrere Zeilen
(a, b, c) -> {
     double s = (a + b + c) / 2;
     return Math.sqrt(s * (s - a) * (s - b) * (s - c));
}

Methodenreferenz

Ich habe es bisher noch nicht verwendet, aber wenn Sie es mit einer Methodenreferenz schreiben können, ist es einfacher, den Code zu sehen, wenn Sie ihn positiv verwenden **. Es ist leicht zu lesen, da Sie keine Variable wie "w" eingeben müssen und wissen, was der Stream-Elementtyp zu diesem Zeitpunkt ist.

Schreiben Sie als class :: method.

Methodenreferenz.java


final int sum = widgets.stream()
                .filter(Widget::isColorRed)
                .mapToInt(Widget::getWeight)
                .sum();

Anonyme Klassen, Lambda-Ausdrücke und Methodenreferenzen sind alle Möglichkeiten, eine funktionale Schnittstelle zu instanziieren. Sie können eine Instanz auf die gleiche Weise erstellen, auch wenn Sie über eine von Ihnen selbst definierte Funktionsschnittstelle verfügen. In einer funktionalen Schnittstelle wissen wir, dass wir nur eine Methode implementieren müssen, sodass auch ein Schreibstil ohne Typinformationen, wie z. B. ein Lambda-Ausdruck, abgeleitet werden kann.

abschließend

Das nächste Mal möchte ich die spezifische Verwendung von Stream vorstellen.

Referenz

Recommended Posts