Це помилка в 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
безпосередньо діє на WhileOp
as, 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
операції, але ніколи не перевіряє, чи скасовано його . Отже, він буде охоче курсувати по всіх елементах цього "підпотоку" перед зупинкою, ймовірно, навіть через решту потоку .