Вопрос-ответ

Limit a stream by a predicate

Ограничить поток предикатом

Существует ли потоковая операция Java 8, которая ограничивает a (потенциально бесконечно) Stream до тех пор, пока первый элемент не будет соответствовать предикату?

В Java 9 мы можем использовать takeWhile как в примере ниже, чтобы вывести все числа меньше 10.

IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);

Поскольку в Java 8 такой операции нет, каков наилучший способ реализовать ее в общем виде?

Переведено автоматически
Ответ 1

Операции takeWhile и dropWhile добавлены в JDK 9. Ваш пример кода

IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);

будет вести себя точно так, как вы ожидаете, при компиляции и запуске в JDK 9.

Выпущен JDK 9. Его можно скачать здесь: Выпуски JDK 9.

Ответ 2

Такая операция должна быть возможной с Java 8 Stream, но это не обязательно может быть выполнено эффективно - например, вы не обязательно можете распараллелить такую операцию, поскольку вам нужно смотреть на элементы по порядку.

API не предоставляет простого способа сделать это, но, вероятно, самый простой способ - взять Stream.iterator(), обернуть Iterator, чтобы получить реализацию "take-while", а затем вернуться к Spliterator а затем к Stream. Или - возможно - обернуть Spliterator, хотя на самом деле его больше нельзя разделить в этой реализации.

Вот непроверенная реализация takeWhile на Spliterator:

static <T> Spliterator<T> takeWhile(
Spliterator<T> splitr, Predicate<? super T> predicate)
{
return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {
boolean stillGoing = true;
@Override public boolean tryAdvance(Consumer<? super T> consumer) {
if (stillGoing) {
boolean hadNext = splitr.tryAdvance(elem -> {
if (predicate.test(elem)) {
consumer.accept(elem);
} else {
stillGoing = false;
}
});
return hadNext && stillGoing;
}
return false;
}
};
}

static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> predicate) {
return StreamSupport.stream(takeWhile(stream.spliterator(), predicate), false);
}
Ответ 3

allMatch() это функция короткого замыкания, поэтому вы можете использовать ее для остановки обработки. Основным недостатком является то, что вам приходится выполнять тест дважды: один раз, чтобы увидеть, следует ли его обрабатывать, и еще раз, чтобы увидеть, стоит ли продолжать.

IntStream
.iterate(1, n -> n + 1)
.peek(n->{if (n<10) System.out.println(n);})
.allMatch(n->n < 10);
Ответ 4

В качестве продолжения ответа @StuartMarks. Моя библиотека StreamEx имеет takeWhile операцию, совместимую с текущей реализацией JDK-9. При запуске под JDK-9 он будет просто делегирован реализации JDK (через MethodHandle.invokeExact что действительно быстро). При запуске под JDK-8 будет использоваться реализация "polyfill". Итак, используя мою библиотеку, проблему можно решить следующим образом:

IntStreamEx.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
java java-8 java-stream