Dies ist ein Fehler in JDK 9 - ab Problem # 8193856 :
takeWhilegeht 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, takeWhilesollte 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 forEachOrderedstattdessen 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, strArrayund ü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 WhileOpswir 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, tob das predicateerfüllt ist:
downstreamIn diesem Fall wird das Element in diesem Fall an die Operation weitergeleitet System.out::println.
- Wenn nicht, wird der Wert
takeauf 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 takeWhileBetrieb. Das andere, was Sie wissen müssen, ist, dass forEachOrdereddie 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 forEachWithCanceldirekt auf das WhileOpAs gearbeitet sinkund 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 forEachWithCanceldie flatMapOperation ausgeführt, die einfach forEachRemainingdas ArraySpliteratorfor 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 hiund fencealles ignorieren , was nur verwendet wird, wenn die Array-Verarbeitung für einen parallelen Stream aufgeteilt wird, ist dies eine einfache forSchleife, die jedes Element an die takeWhileOperation ü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 .