[JAVA] Lassen Sie uns etwas tiefer in die Stream-API eintauchen, von der ich verstehe, dass sie neu geschrieben wurde.

Was bedeutet dies beim Schreiben der Stream-API von Java? Hier ist eine kleine Zusammenfassung dessen, worüber ich mich gewundert habe. Es handelt sich nicht um eine Sammlung von Stream-API-Tipps. Es tut mir leid, wenn Sie den Artikel mit solchen Erwartungen lesen.

Zusammenfassung dieses Artikels

Sie können sich ein wenig ein Bild von diesem Element machen, indem Sie diesen Artikel lesen.

Ziel dieses Artikels

Lernen Sie die grammatikalischen Elemente der Stream-API kennen, die Sie irgendwie verstehen.

Wie kann man die Stream-API überhaupt schreiben?

Die Stream-API wird im Falle einer Verarbeitung wie der Anzeige nur gerader Zahlen von 1 bis 10 wie folgt geschrieben.

Arrays.stream(new int[]{1,2,3,4,5,6,7,8,9,10})
     .filter( i -> (i % 2) == 0 )
     .forEach( i -> System.out.println(i));

(Es gibt auch eine coole Form mit IntStream.range, aber lass es in Ruhe ...) Übrigens lautet die herkömmliche Schreibweise für und if-Anweisungen wie folgt.

for(int i : new int[]{1,2,3,4,5,6,7,8,10}) {
     if((i % 2) == 0) {
         System.out.println(i);
     }
}

Bei einem so kurzen Vorgang ist die Anzahl der Zeilen gleich und es sieht gleich aus. Wenn es jedoch zu einem komplizierten Prozess wird, wenn Sie sich an Methoden wie filter und forEach of Stream API erinnern, Ich persönlich denke, dass es einfacher sein wird, zu sehen und zu verstehen, weil klar ist, wo und was Sie tun. Aber wenn Sie es plötzlich betrachten, was ist dieses Argument? Was ist der Unterschied zwischen Filter und für jeden? Weil es wird Ich habe versucht, ein wenig zusammenzufassen.

Funktionsschnittstelle

Der Argumentteil, den Sie in der obigen Quelle gesehen haben, ist Ihnen zum ersten Mal unbekannt.

filter( i -> (i % 2) == 0 )

Tatsächlich ist dies eine Schreibweise, die eine funktionale Schnittstelle als Argument verwendet und mit einem Lambda-Ausdruck abgekürzt wird. Ich denke, dass diejenigen, die hauptsächlich vor Java7 arbeiten, es zum ersten Mal sehen werden. In Java8 und höher sind diejenigen, die die Bedingungen der Funktionstypschnittstelle erfüllen, Lambda-Ausdrücke, die die Verarbeitung der Funktion direkt als Argument festlegen. Sie können es so schreiben.

Was ist die Definition einer funktionalen Schnittstelle?

Grob gesagt handelt es sich um eine Schnittstelle, in der nur eine abstrakte Methode definiert ist. Ein Beispiel ist so.

@FunctionalInterface
public interface Predicate<T> {
     boolean test(T t);
}

Wie Sie sehen können, ist es nur eine Schnittstelle. Es ist wichtig, dass es nur eine abstrakte Methode gibt, und sie kann als Lambda-Ausdruck geschrieben werden, der nur die Verarbeitung in dieser Form beschreibt. (Es spielt keine Rolle, ob die Standardmethode geschrieben ist.) Die funktionale Schnittstelle gibt übrigens nur explizit an, dass sie mit funktionalen Schnittstellen verwendet werden kann. Es ist nicht prozessual sinnvoll.

Legen Sie eine funktionale Schnittstelle als Argument mit einem Lambda-Ausdruck fest

Wenn Sie auf die Filtermethode verweisen, empfängt filter die Prädikatschnittstelle als Argument.

  Stream<T> filter(Predicate<? super T> predicate);

Im obigen Quellcode wurde es so geschrieben.

filter( i -> (i % 2) == 0 )

Die abstrakte Methode von Predicate ist die Testmethode. Geben Sie einfach den Prozess an, bei dem ein Argument empfangen und der Boolesche Wert als Rückgabewert im Lambda-Ausdruck zurückgegeben wird. (Eigentlich ist das obige Beispiel das.) Doch selbst wenn gesagt wird, dass der obige Schreibstil der Lambda-Stil ist, was ist der Lambda-Stil? ?? ?? Es wird sein Werfen wir einen kurzen Blick auf die Grammatik.

Lambda-Stil

Die grundlegende Grammatik eines Lambda-Ausdrucks sieht so aus.

(Streit) -> {wird bearbeitet}

Es ist, als würde man ein Argument auf die linke Seite des Pfeils setzen und den Prozess auf die rechte Seite schreiben. Mit anderen Worten, wenn Sie sich den Lambda-Ausdruck des Filters ansehen, den ich dieses Mal geschrieben habe, ohne ihn wegzulassen, sieht er so aus.

filter( (int  i) -> {return (i % 2) == 0;} )

Selbst wenn Sie mit diesem Schreibstil noch nicht vertraut sind, können Sie möglicherweise ein Urteil fällen. Im Beispiel oben habe ich einfach alles weggelassen, was den Argumenttyp weglassen kann und so weiter. (Ich denke, die Regel, den Lambda-Ausdruck wegzulassen, wird im Artikel erneut angesprochen.)

Einfach ausgedrückt, der Argumenttyp und () werden weggelassen, und die return-Anweisung und {} werden weggelassen. Alle sind bedingt, aber wenn Sie sie weglassen, verbessern sich die Aussichten. Sie können es verstehen, indem Sie Grammatik studieren. Ich denke, es ist eine gute Idee, die Teile wegzulassen, die weggelassen werden können.

Arten von Funktionsschnittstellen

Standardmäßig sind verschiedene Funktionsschnittstellen definiert. (Effective Java empfiehlt übrigens nicht, eine eigene Funktionsschnittstelle zu erstellen Versuchen Sie bei der Verwendung die Standardversion. ) Es kann jedoch grob in vier Systeme unterteilt werden. Danach widmet es sich int, long und primitiven Dingen und hat zwei Argumente. Es ist eine gute Idee, sich die Grundlagen zu merken und sie von Fall zu Fall nachzuschlagen. (Ich konnte es nicht in meinem Kopf tun, weil es so viele gibt ...)

Erinnern wir uns zunächst an die Existenz und die Methoden des grundlegenden Lieferanten, Verbrauchers, Prädikats und der Funktion.

//Lieferantenschnittstelle
//Es definiert eine get-Methode, die kein Argument empfängt und einen Wert zurückgibt.
@FunctionalInterface
public interface Supplier<T> {
    T get();
}

//Verbraucherschnittstelle
//Definiert eine accept-Methode, die ein Argument akzeptiert und keinen Rückgabewert zurückgibt.
@FunctionalInterface
public interface Consumer<T> {
     void accept(T t);
}

//Prädikatschnittstelle
//Es definiert eine Testmethode, die ein Argument akzeptiert und einen Booleschen Wert zurückgibt.
@FunctionalInterface
public interface Predicate<T> {
     boolean test(T t);
};

//Funktionsschnittstelle
//Es definiert eine Apply-Methode, die ein Argument eines beliebigen Typs verwendet und einen Rückgabewert eines beliebigen Typs zurückgibt.
@FunctionalInterface
public interface Function<T, R> {
     R apply(T t);
}

Übrigens, wenn Sie alle Arten sehen möchten, wird empfohlen, das Paket "java.util.function" zu überprüfen.

Zusammenfassung der Funktionsschnittstellen

Zwischenverarbeitung und Kündigungsverarbeitung

Der Stream-API-Verarbeitungsablauf ist hauptsächlich in drei Abläufe unterteilt.

  1. Stream-Generierung Auch dieses Mal wird Arrays.stream () verwendet, um den Stream zu generieren. Es wird nur ein Stream erstellt, sodass ich nicht ins Detail gehe.
  2. Zwischenverarbeitung In diesem Fall ist der Filter anwendbar. Die Zwischenverarbeitung kann in einem Stream mehrmals durchgeführt werden.
  3. Kündigungsprozess In diesem Fall gilt für Each. Die Beendigung kann in einem Stream nur einmal aufgerufen werden.

Der Verarbeitungsablauf der Stream-API ist Generierung → Zwischenverarbeitung → Beendigungsverarbeitung. Aber warum kann der Zwischenprozess mehrmals und die Beendigungsmethode nur einmal ausgeführt werden? So viele Informationen aus allgemeinen Erklärungen können verwirrend sein.

Separate Zwischenverarbeitung und Terminierungsverarbeitung

Um ehrlich zu sein, ist es schwierig, sich nur die Zwischenverarbeitungs- und Terminierungsverarbeitungsmethoden zu merken und zu unterscheiden. (An wie viele sollte ich mich erinnern ...) Es ist wichtiger, den Ablauf der Zwischen- und Terminierungsverarbeitung zu verstehen, als sich an die Methode zu erinnern.

Es erscheint oft, aber wieder der gleiche Quellcode.

Arrays.stream(new int[]{1,2,3,4,5,6,7,8,9,10})
     .filter( i -> (i % 2) == 0 )
     .forEach( i -> System.out.println(i));

Wichtig hierbei ist, dass Sie die forEach-Methode nach der Filtermethode aufrufen können. Übrigens funktioniert es nicht, auch wenn ich es so schreibe.

//Schlechtes Beispiel(Oder besser gesagt, ein Kompilierungsfehler)
Arrays.stream(new int[]{1,2,3,4,5,6,7,8,9,10})
     .forEach( i -> System.out.println(i));
     .filter( i -> (i % 2) == 0 )

Tatsächlich kann der Grund, warum die forEach-Methode nach der Filtermethode aufgerufen werden kann, direkt anhand der Stream-Schnittstelle verstanden werden.

Zwischenverarbeitung und Terminierungsverarbeitung, verstanden von der Stream-Schnittstelle

Der folgende Quellcode ist ein Auszug aus der abstrakten Methode der Stream-Schnittstelle.

public interface Stream<T> extends BaseStream<T, Stream<T>> {
     //Rückgabewert ist Stream
     Stream<T> filter(Predicate<? super T> predicate);
     <R> Stream<R> map(Function<? super T, ? extends R> mapper);
     IntStream mapToInt(ToIntFunction<? super T> mapper);
     LongStream mapToLong(ToLongFunction<? super T> mapper);
     DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
     <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>>
mapper);
     IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
     LongStream flatMapToLong(Function<? super T, ? extends LongStream>
mapper);
     DoubleStream flatMapToDouble(Function<? super T, ? extends DoubleStream>
mapper);
     Stream<T> distinct();
     Stream<T> sorted();
     Stream<T> sorted(Comparator<? super T> comparator);
     Stream<T> peek(Consumer<? super T> consumer);
     Stream<T> limit(long maxSize);
     Stream<T> substream(long startInclusive);
     Stream<T> substream(long startInclusive, long endExclusive);

     //Verschiedene Rückgabewerte
     void forEach(Consumer<? super T> action);
     void forEachOrdered(Consumer<? super T> action);
     Object[] toArray();
     <A> A[] toArray(IntFunction<A[]> generator);
     T reduce(T identity, BinaryOperator<T> accumulator);
     Optional<T> reduce(BinaryOperator<T> accumulator);
     <U> U reduce(U identity,
                  BiFunction<U, ? super T, U> accumulator,
                  BinaryOperator<U> combiner);
     <R> R collect(Supplier<R> resultFactory,
                   BiConsumer<R, ? super T> accumulator,
                   BiConsumer<R, R> combiner);
     <R> R collect(Collector<? super T, R> collector);
     Optional<T> min(Comparator<? super T> comparator);
     Optional<T> max(Comparator<? super T> comparator);
     long count();
     boolean anyMatch(Predicate<? super T> predicate);
     boolean allMatch(Predicate<? super T> predicate);
     boolean noneMatch(Predicate<? super T> predicate);
     Optional<T> findFirst();
     Optional<T> findAny();
}

Wenn Sie genau hinschauen, verwendet die Filtermethode Stream als Rückgabewert. Und forEach hat keinen Rückgabewert, oder?

Dies! (Welcher)

Dies ist eine Schnittstelle, aber wenn Sie sich die Implementierungsklasse "Referenzpipeline" ansehen Zwischenmethoden wie die Filtermethode erstellen einen neuen Stream in der Methode und legen ihn als Rückgabewert fest. Da Stream als Rückgabewert festgelegt ist, kann die Stream-Methode kontinuierlich aufgerufen werden. Im Gegenteil, wie Sie am Rückgabewert der Methode sehen können, setzt die Terminalmethode wie forEach Stream nicht als Rückgabewert. Sie können die Stream-Methode nicht nacheinander aufrufen.

Wenn Sie dies verstehen, sind Zwischenmethoden und Beendigungsmethoden sehr einfach. Die Methode, die Stream zurückgibt, ist der Zwischenprozess Es kann grob als Beendigungsverarbeitung verstanden werden, dass Stream nicht als Rückgabewert verwendet wird.

Zusammenfassung der Zwischenverarbeitung und Terminierungsverarbeitung

Zusammenfassung

Wenn Sie verstehen, ist die Stream-API nicht beängstigend.

TODO

Recommended Posts

Lassen Sie uns etwas tiefer in die Stream-API eintauchen, von der ich verstehe, dass sie neu geschrieben wurde.
[Swift, ein Muss für Jungvögel! ] Lassen Sie uns die Implementierung der Kommunikationsverarbeitung der Web-API verstehen
Java: Das Problem ist schneller, Stream oder Loop
Betrachten wir die Bedeutung von "Stream" und "Collect" in der Stream-API von Java.
[java8] Um die Stream-API zu verstehen
Eine Geschichte, die ich mit der Stream-API von Java8 einem Prozess schreiben wollte, der einer while-Anweisung entspricht
Ich habe versucht, die Stream-API zusammenzufassen
Kanban ist nicht die einzige Lösung. Verwenden wir Microsoft Project etwas mehr