TL; DR, це було розглянуто в JDK-8075939 та виправлено в Java 10 (і повернуто до Java 8 у JDK-8225328 ).
Розглядаючи реалізацію ( ReferencePipeline.java
), ми бачимо метод [ посилання ]
@Override
final void forEachWithCancel(Spliterator<P_OUT> spliterator, Sink<P_OUT> sink) {
do { } while (!sink.cancellationRequested() && spliterator.tryAdvance(sink));
}
який буде викликаний для findFirst
роботи. Особливе, про що слід подбати, це те, sink.cancellationRequested()
що дозволяє закінчити цикл на першому матчі. Порівняти із [ посиланням ]
@Override
public final <R> Stream<R> flatMap(Function<? super P_OUT, ? extends Stream<? extends R>> mapper) {
Objects.requireNonNull(mapper);
return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT | StreamOpFlag.NOT_SIZED) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
return new Sink.ChainedReference<P_OUT, R>(sink) {
@Override
public void begin(long size) {
downstream.begin(-1);
}
@Override
public void accept(P_OUT u) {
try (Stream<? extends R> result = mapper.apply(u)) {
if (result != null)
result.sequential().forEach(downstream);
}
}
};
}
};
}
Метод просування одного елемента закінчується викликом forEach
підпотоку без можливості попереднього завершення, а коментар на початку flatMap
методу навіть розповідає про цю відсутність функції.
Оскільки це більше, ніж просто оптимізація, оскільки це передбачає, що код просто ламається, коли підпотік нескінченний, я сподіваюся, що розробники незабаром докажуть, що вони "можуть зробити краще, ніж це" ...
Для ілюстрації наслідків, хоч і Stream.iterate(0, i->i+1).findFirst()
працює, як очікувалося, Stream.of("").flatMap(x->Stream.iterate(0, i->i+1)).findFirst()
опиниться в нескінченному циклі.
Що стосується специфікації, більшу частину її можна знайти в
розділ "Потокові операції та трубопроводи" специфікації пакету :
...
Проміжні операції повертають новий потік. Вони завжди ліниві ;
...
... Лінощі також дозволяють уникати вивчення всіх даних, коли це не потрібно; для таких операцій, як "знайти перший рядок довшим за 1000 символів", необхідно лише вивчити достатньо рядків, щоб знайти той, який має бажані характеристики, не вивчаючи всі рядки, доступні з джерела. (Ця поведінка стає ще важливішою, коли вхідний потік нескінченний і не просто великий.)
...
Крім того, деякі операції вважаються операціями короткого замикання . Проміжна операція є коротким замиканням, якщо, подавшись з нескінченним входом, вона може в результаті створити кінцевий потік. Операція терміналу є короткозамкненою, якщо, якщо вона представлена з нескінченним входом, вона може закінчитися за скінченний час. Операція короткого замикання в трубопроводі є необхідною, але недостатньою умовою для того, щоб обробка нескінченного потоку нормально закінчувалася за скінчений час.
Зрозуміло, що операція короткого замикання не гарантує кінцевого припинення часу, наприклад, коли фільтр не відповідає жодному елементу, який обробка не може завершити, але реалізація, яка не підтримує жодного припинення в обмежений час, просто ігноруючи короткозамкнений характер операції далеко не відповідає специфікації.