Це помилка в JDK 9 - з випуску # 8193856 :
takeWhileнеправильно припускає, що попередня операція підтримує та відзначає скасування, що, на жаль, не стосується flatMap.
Пояснення
Якщо потік упорядкований, takeWhileслід показати очікувану поведінку. Це не зовсім так у вашому коді, оскільки ви використовуєте forEach, який відмовляється від замовлення. Якщо ви піклуєтесь про це, що ви робите в цьому прикладі, forEachOrderedзамість цього вам слід скористатися . Кумедна річ: це нічого не змінює. 🤔
То, може, потік спочатку не замовлений? (У цьому випадку поведінка нормальна .) Якщо ви створите тимчасову змінну для потоку, створеного з, strArrayі перевірите, чи впорядковано це, виконавши вираз ((StatefulOp) stream).isOrdered();у точці розриву, ви виявите, що він справді впорядкований:
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);
Це означає, що це, швидше за все, помилка впровадження.
У кодекс
Як підозрювали інші, я тепер також думаю, що це може бути пов’язано з flatMapбажанням. Точніше, обидві проблеми можуть мати однакову першопричину.
Заглядаючи у джерело WhileOps, ми можемо побачити такі методи:
@Override
public void accept(T t) {
if (take = predicate.test(t)) {
downstream.accept(t);
}
}
@Override
public boolean cancellationRequested() {
return !take || downstream.cancellationRequested();
}
Цей код використовується takeWhileдля перевірки для заданого елемента потоку, tчи predicateвиконується:
- Якщо так, він передає елемент
downstreamоперації, в даному випадку System.out::println.
- Якщо ні, він встановлює
takeзначення false, тому, коли наступного разу запитується, чи слід скасовувати конвеєр (тобто це зроблено), він повертається true.
Це охоплює takeWhileоперацію. Інше, що вам потрібно знати, це те, що forEachOrderedпризводить до операції терміналу, що виконує метод 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;
}
Все це робить:
- перевірити, чи не було скасовано газопровід
- якщо ні, пересуньте раковину на один елемент
- зупинити, якщо це був останній елемент
Виглядає багатообіцяюче, правда?
Без flatMap
У "хорошому випадку" (без flatMap; ваш другий приклад) forEachWithCancelбезпосередньо діє на WhileOpas, sinkі ви можете бачити, як це відбувається:
ReferencePipeline::forEachWithCancel робить свій цикл:
WhileOps::accept дається кожен елемент потоку
WhileOps::cancellationRequested запитується після кожного елемента
- в якийсь момент
"Sample4"не вдається предикат, і потік скасовується
Ага!
С flatMap
У «поганий випадок» (з flatMap, ваш перший приклад), forEachWithCancelдіє на flatMapоперації, хоча ,, який просто викликає forEachRemainingна ArraySpliteratorпротягом {"Sample3", "Sample4", "Sample5"}, який робить це:
if ((a = array).length >= (hi = fence) &&
(i = index) >= 0 && i < (index = hi)) {
do { action.accept((T)a[i]); } while (++i < hi);
}
Ігноруючи все це hiта fenceінше, що використовується лише в тому випадку, якщо обробка масиву розділена на паралельний потік, це простий forцикл, який передає кожен елемент takeWhileоперації, але ніколи не перевіряє, чи скасовано його . Отже, він буде охоче курсувати по всіх елементах цього "підпотоку" перед зупинкою, ймовірно, навіть через решту потоку .