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.
Sie können sich ein wenig ein Bild von diesem Element machen, indem Sie diesen Artikel lesen.
Lernen Sie die grammatikalischen Elemente der Stream-API kennen, die Sie irgendwie verstehen.
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.
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.
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.
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.
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.
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.
Der Stream-API-Verarbeitungsablauf ist hauptsächlich in drei Abläufe unterteilt.
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.
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.
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.
Wenn Sie verstehen, ist die Stream-API nicht beängstigend.
TODO
Recommended Posts