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 .