Limitar una secuencia por un predicado
¿Existe una operación de flujo de Java 8 que limite a (potencialmente infinito) Stream
hasta que el primer elemento no coincida con un predicado?
En Java 9 podemos usar takeWhile
como en el siguiente ejemplo para imprimir todos los números menores de 10.
IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
Como no existe tal operación en Java 8, ¿cuál es la mejor manera de implementarla de manera general?
Operaciones takeWhile
y dropWhile
se han agregado a JDK 9. Su código de ejemplo
IntStream
.iterate(1, n -> n + 1)
.takeWhile(n -> n < 10)
.forEach(System.out::println);
se comportará exactamente como espera cuando se compila y ejecuta en JDK 9.
JDK 9 ha sido lanzado. Está disponible para descargar aquí: Lanzamientos de JDK 9 .
Tal operación debería ser posible con Java 8 Stream
, pero no necesariamente se puede hacer de manera eficiente; por ejemplo, no necesariamente se puede paralelizar dicha operación, ya que hay que observar los elementos en orden.
La API no proporciona una manera fácil de hacerlo, pero probablemente la forma más sencilla es tomar Stream.iterator()
, empaquetar el Iterator
para tener una implementación "durante un tiempo" y luego volver a a Spliterator
y luego a Stream
. O, tal vez, envolver el archivo Spliterator
, aunque ya no se puede dividir en esta implementación.
Aquí hay una implementación no probada de takeWhile
en 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);
}
allMatch()
es una función de cortocircuito, por lo que puede usarla para detener el procesamiento. La principal desventaja es que tienes que hacer la prueba dos veces: una para ver si debes procesarla y otra para ver si debes continuar.
IntStream
.iterate(1, n -> n + 1)
.peek(n->{if (n<10) System.out.println(n);})
.allMatch(n->n < 10);