Dies ist ein Fehler in JDK 9 - ab Problem # 8193856 :
takeWhile
geht fälschlicherweise davon aus, dass eine vorgelagerte Operation die Stornierung unterstützt und honoriert, was leider nicht der Fall ist flatMap
.
Erläuterung
Wenn der Stream bestellt wird, takeWhile
sollte das erwartete Verhalten angezeigt werden. Dies ist in Ihrem Code nicht ganz der Fall, da Sie verwenden forEach
, wodurch auf die Bestellung verzichtet wird. Wenn Sie sich dafür interessieren, was Sie in diesem Beispiel tun, sollten Sie forEachOrdered
stattdessen verwenden. Lustige Sache: Das ändert nichts. 🤔
Vielleicht ist der Stream also gar nicht erst bestellt? (In diesem Fall ist das Verhalten in Ordnung .) Wenn Sie eine temporäre Variable für den Stream erstellen, aus dem erstellt wurde, strArray
und überprüfen, ob sie geordnet ist, indem Sie den Ausdruck ((StatefulOp) stream).isOrdered();
am Haltepunkt ausführen , werden Sie feststellen, dass er tatsächlich geordnet ist:
String[][] strArray = {{"Sample1", "Sample2"}, {"Sample3", "Sample4", "Sample5"}};
Stream<String> stream = Arrays.stream(strArray)
.flatMap(indStream -> Arrays.stream(indStream))
.takeWhile(ele -> !ele.equalsIgnoreCase("Sample4"));
System.out.println(stream);
Dies bedeutet, dass dies sehr wahrscheinlich ein Implementierungsfehler ist.
In den Code
Wie andere vermutet haben, denke ich jetzt auch, dass dies mit Eifer verbunden sein könnteflatMap
. Genauer gesagt können beide Probleme dieselbe Grundursache haben.
Wenn WhileOps
wir uns die Quelle von ansehen, können wir folgende Methoden erkennen:
@Override
public void accept(T t) {
if (take = predicate.test(t)) {
downstream.accept(t);
}
}
@Override
public boolean cancellationRequested() {
return !take || downstream.cancellationRequested();
}
Dieser Code wird verwendet takeWhile
, um für ein bestimmtes Stream-Element zu überprüfen, t
ob das predicate
erfüllt ist:
downstream
In diesem Fall wird das Element in diesem Fall an die Operation weitergeleitet System.out::println
.
- Wenn nicht, wird der Wert
take
auf false gesetzt. Wenn Sie das nächste Mal gefragt werden, ob die Pipeline abgebrochen werden soll (dh, dies ist erledigt), wird sie zurückgegeben true
.
Dies umfasst den takeWhile
Betrieb. Das andere, was Sie wissen müssen, ist, dass forEachOrdered
die Terminaloperation die Methode ausführt ReferencePipeline::forEachWithCancel
:
@Override
final boolean forEachWithCancel(Spliterator<P_OUT> spliterator, Sink<P_OUT> sink) {
boolean cancelled;
do { } while (
!(cancelled = sink.cancellationRequested())
&& spliterator.tryAdvance(sink));
return cancelled;
}
Alles was dies tut ist:
- Überprüfen Sie, ob die Pipeline abgebrochen wurde
- Wenn nicht, stellen Sie die Spüle um ein Element vor
- Hör auf, wenn dies das letzte Element war
Sieht vielversprechend aus, oder?
Ohne flatMap
Im "guten Fall" (ohne flatMap
; Ihr zweites Beispiel) wird forEachWithCancel
direkt auf das WhileOp
As gearbeitet sink
und Sie können sehen, wie sich dies auswirkt:
ReferencePipeline::forEachWithCancel
macht seine Schleife:
WhileOps::accept
wird jedes Stream-Element gegeben
WhileOps::cancellationRequested
wird nach jedem Element abgefragt
- Irgendwann
"Sample4"
schlägt das Prädikat fehl und der Stream wird abgebrochen
Yay!
Mit flatMap
Im "schlechten Fall" (mit flatMap
; Ihrem ersten Beispiel) wird jedoch forEachWithCancel
die flatMap
Operation ausgeführt, die einfach forEachRemaining
das ArraySpliterator
for aufruft {"Sample3", "Sample4", "Sample5"}
, was Folgendes bewirkt:
if ((a = array).length >= (hi = fence) &&
(i = index) >= 0 && i < (index = hi)) {
do { action.accept((T)a[i]); } while (++i < hi);
}
Wenn Sie all das hi
und fence
alles ignorieren , was nur verwendet wird, wenn die Array-Verarbeitung für einen parallelen Stream aufgeteilt wird, ist dies eine einfache for
Schleife, die jedes Element an die takeWhile
Operation übergibt , aber niemals prüft, ob es abgebrochen wird . Es wird daher eifrig alle Elemente in diesem "Teilstrom" durchlaufen, bevor es stoppt, wahrscheinlich sogar durch den Rest des Stroms .
String[][] strArray = { {"Sample1", "Sample2"}, {"Sample3", "Sample4"}, {"Sample5", "Sample6"}, };
als Eingabe, und es scheint zu funktionieren. Wenn nur ein Zwischenelement übereinstimmt, führtflatMap
die Unkenntnis zur Löschung dazu, dass das Flag bei der Auswertung des nachfolgenden Elements überschrieben wird.Dies ist ein Fehler, egal wie ich ihn betrachte - und danke Holger für Ihre Kommentare. Ich wollte diese Antwort hier nicht einfügen (ernsthaft!), Aber keine der Antworten besagt eindeutig, dass dies ein Fehler ist.
Die Leute sagen, dass dies mit bestellt / unbestellt sein muss, und dies ist nicht wahr, da dies
true
dreimal gemeldet wird:Stream<String[]> s1 = Arrays.stream(strArray); System.out.println(s1.spliterator().hasCharacteristics(Spliterator.ORDERED)); Stream<String> s2 = Arrays.stream(strArray) .flatMap(indStream -> Arrays.stream(indStream)); System.out.println(s2.spliterator().hasCharacteristics(Spliterator.ORDERED)); Stream<String> s3 = Arrays.stream(strArray) .flatMap(indStream -> Arrays.stream(indStream)) .takeWhile(ele -> !ele.equalsIgnoreCase("Sample4")); System.out.println(s3.spliterator().hasCharacteristics(Spliterator.ORDERED));
Es ist auch sehr interessant, wenn Sie es ändern zu:
String[][] strArray = { { "Sample1", "Sample2" }, { "Sample3", "Sample5", "Sample4" }, // Sample4 is the last one here { "Sample7", "Sample8" } };
dann
Sample7
undSample8
wird nicht Teil der Ausgabe sein, sonst werden sie. Es scheint, dass ein Abbruch-Flagflatmap
ignoriert wird , das von eingeführt würdedropWhile
.quelle
Wenn Sie sich die Dokumentation
takeWhile
ansehen für :Ihr Stream ist zufällig bestellt,
takeWhile
weiß aber nicht, dass dies der Fall ist. Als solches gibt es die 2. Bedingung zurück - die Teilmenge. DutakeWhile
benimmst dich nur wie einfilter
.Wenn Sie einen Anruf hinzuzufügen ,
sorted
vortakeWhile
, werden Sie sehen das Ergebnis Sie erwarten:Arrays.stream(strArray) .flatMap(indStream -> Arrays.stream(indStream)) .sorted() .takeWhile(ele -> !ele.equalsIgnoreCase("Sample4")) .forEach(ele -> System.out.println(ele));
quelle
Stream<String[]> s1 = Arrays.stream(strArray); System.out.println(s1.spliterator().hasCharacteristics(Spliterator.ORDERED))
und so weiter für jeden Schritt machen - sie werden alle einenORDERED
Stream produzieren, sieht dies aus wie ein Fehler, der noch nicht gemeldet wurde.sorted().unordered() .takeWhile(…)
immer noch das Richtige dann tun? Ich würde sagen, es liegt daran, dass es sichsorted
um eine zustandsbehaftete Operation handelt, die die gesamte Eingabe puffert, gefolgt von einer wirklich faulen Iteration.takeWhile
sollten Sie auf die Elemente, die es tatsächlich empfängt, in der Reihenfolge reagieren, in der es sie empfängt, und aufhören, sobald ein Element sein Prädikat nicht erfüllt. Wenn man nach einem ungeordneten Stream filtern möchte, sollte man ihn verwendenfilter
.Der Grund dafür ist, dass die
flatMap
Operation auch eine Zwischenoperation ist, bei der (eine von) der zustandsbehafteten Kurzschluss-ZwischenoperationtakeWhile
verwendet wird.Das Verhalten
flatMap
von Holger in dieser Antwort ist sicherlich eine Referenz, die man sich nicht entgehen lassen sollte, um die unerwartete Ausgabe für solche Kurzschlussoperationen zu verstehen.Ihr erwartetes Ergebnis kann erzielt werden, indem Sie diese beiden Zwischenoperationen aufteilen, indem Sie eine Terminaloperation einführen, um einen geordneten Stream deterministisch weiter zu verwenden, und sie für eine Stichprobe ausführen als:
List<String> sampleList = Arrays.stream(strArray).flatMap(Arrays::stream).collect(Collectors.toList()); sampleList.stream().takeWhile(ele -> !ele.equalsIgnoreCase("Sample4")) .forEach(System.out::println);
Außerdem scheint es einen verwandten Fehler # JDK-8075939 zu geben, um dieses bereits registrierte Verhalten zu verfolgen.
Bearbeiten : Dies kann weiter verfolgt werden, indem JDK-8193856 als Fehler akzeptiert wird.
quelle
flatMap
Betrieb abzuschließen, und dann den Stream zur Ausführung zu verarbeitentakeWhile
.