cefalópodo
¿Cómo puedo comprobar si un Stream
está vacío y lanza una excepción si no lo está, como una operación no terminal?
Básicamente, estoy buscando algo equivalente al siguiente código, pero sin materializar el flujo intermedio. En particular, la verificación no debe ocurrir antes de que una operación de terminal consuma realmente el flujo.
public Stream<Thing> getFilteredThings() {
Stream<Thing> stream = getThings().stream()
.filter(Thing::isFoo)
.filter(Thing::isBar);
return nonEmptyStream(stream, () -> {
throw new RuntimeException("No foo bar things available")
});
}
private static <T> Stream<T> nonEmptyStream(Stream<T> stream, Supplier<T> defaultValue) {
List<T> list = stream.collect(Collectors.toList());
if (list.isEmpty()) list.add(defaultValue.get());
return list.stream();
}
Esto puede ser suficiente en muchos casos.
stream.findAny().isPresent()
-
Solución simple y nítida. Este código consumirá el flujo, por lo que tendremos que hacer otro flujo si queremos iterar cuando no esté vacío.
– Harish
5 de marzo de 2021 a las 20:28
-
Si tienes una última
filter()
operación antes de tener quecount()
. puedes reemplazar la secuencia...filter(expr.findAny().isPresent());
por...anyMatch(expr)
– Juanmf
4 de mayo a las 1:14
Las otras respuestas y comentarios son correctos en el sentido de que para examinar el contenido de una secuencia, se debe agregar una operación de terminal, “consumiendo” así la secuencia. Sin embargo, uno puede hacer esto y volver a convertir el resultado en una secuencia, sin almacenar en búfer todo el contenido de la secuencia. Aqui hay un par de ejemplos:
static <T> Stream<T> throwIfEmpty(Stream<T> stream) {
Iterator<T> iterator = stream.iterator();
if (iterator.hasNext()) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
} else {
throw new NoSuchElementException("empty stream");
}
}
static <T> Stream<T> defaultIfEmpty(Stream<T> stream, Supplier<T> supplier) {
Iterator<T> iterator = stream.iterator();
if (iterator.hasNext()) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false);
} else {
return Stream.of(supplier.get());
}
}
Básicamente, convierte el flujo en un Iterator
para llamar hasNext()
en él, y si es cierto, encienda el Iterator
volver a un Stream
. Esto es ineficiente en el sentido de que todas las operaciones subsiguientes en el flujo pasarán por el iterador. hasNext()
y next()
métodos, lo que también implica que la secuencia se procesa efectivamente de forma secuencial (incluso si luego se vuelve paralela). Sin embargo, esto le permite probar la secuencia sin almacenar en búfer todos sus elementos.
Probablemente hay una manera de hacer esto usando un Spliterator
en lugar de un Iterator
. Potencialmente, esto permite que el flujo devuelto tenga las mismas características que el flujo de entrada, incluida la ejecución en paralelo.
-
No creo que haya una solución mantenible que admita un procesamiento paralelo eficiente, ya que es difícil admitir la división, sin embargo, tener
estimatedSize
ycharacteristics
incluso podría mejorar el rendimiento de un solo subproceso. Sucedió que escribí elSpliterator
solución mientras estabas publicando elIterator
solución…– Holger
30/10/2014 a las 17:42
-
Puede pedirle a la transmisión un Spliterator, llamar a tryAdvance(lambda) donde su lambda captura todo lo que se le pasa, y luego devolver un Spliterator que delega casi todo al Spliterator subyacente, excepto que pega el primer elemento de nuevo en el primer fragmento ( y corrige el resultado de estimar tamaño).
– Brian Goetz
30/10/2014 a las 20:55
-
@BrianGoetz Sí, eso fue lo que pensé, solo que aún no me he molestado en hacer el trabajo preliminar de manejar todos esos detalles.
– Marcas de Stuart
30/10/2014 a las 21:19
-
@Brian Goetz: Eso es lo que quise decir con “demasiado complicado”. Vocación
tryAdvance
antes deStream
¿Convierte la naturaleza perezosa delStream
en una secuencia “parcialmente perezosa”. También implica que buscar el primer elemento ya no es una operación paralela, ya que primero debe dividir y hacertryAdvance
en las partes divididas al mismo tiempo para hacer una operación paralela real, por lo que entendí. Si la única operación terminal esfindAny
o similar que destruiría todo elparallel()
solicitud.– Holger
31/10/2014 a las 10:41
-
Entonces, para soporte paralelo completo, no debe llamar
tryAdvance
antes de que la transmisión lo haga y tenga que envolver cada parte dividida en un proxy y recopilar la información “hasAny” de todas las operaciones simultáneas por su cuenta y asegurarse de que la última operación simultánea arroje la excepción deseada si la transmisión estaba vacía. Un montón de cosas…– Holger
31/10/2014 a las 10:45
Holger
Si puede vivir con capacidades paralelas limitadas, la siguiente solución funcionará:
private static <T> Stream<T> nonEmptyStream(
Stream<T> stream, Supplier<RuntimeException> e) {
Spliterator<T> it=stream.spliterator();
return StreamSupport.stream(new Spliterator<T>() {
boolean seen;
public boolean tryAdvance(Consumer<? super T> action) {
boolean r=it.tryAdvance(action);
if(!seen && !r) throw e.get();
seen=true;
return r;
}
public Spliterator<T> trySplit() { return null; }
public long estimateSize() { return it.estimateSize(); }
public int characteristics() { return it.characteristics(); }
}, false);
}
Aquí hay un código de ejemplo usándolo:
List<String> l=Arrays.asList("hello", "world");
nonEmptyStream(l.stream(), ()->new RuntimeException("No strings available"))
.forEach(System.out::println);
nonEmptyStream(l.stream().filter(s->s.startsWith("x")),
()->new RuntimeException("No strings available"))
.forEach(System.out::println);
El problema con la ejecución paralela (eficiente) es que admitir la división del Spliterator
requiere una forma segura para subprocesos de notar si alguno de los fragmentos ha visto algún valor de una manera segura para subprocesos. Entonces el último de los fragmentos ejecutándose tryAdvance
tiene que darse cuenta de que es el último (y tampoco pudo avanzar) para lanzar la excepción apropiada. Así que no agregué soporte para dividir aquí.
Eran
Debe realizar una operación de terminal en el Stream para que se aplique cualquiera de los filtros. Por lo tanto, no puedes saber si estará vacío hasta que lo consumas.
Lo mejor que puede hacer es terminar el Stream con un findAny()
operación de terminal, que se detendrá cuando encuentre algún elemento, pero si no hay ninguno, tendrá que iterar sobre toda la lista de entrada para encontrarlo.
Esto solo lo ayudaría si la lista de entrada tiene muchos elementos, y uno de los primeros pasa los filtros, ya que solo se tendría que consumir un pequeño subconjunto de la lista antes de saber que Stream no está vacío.
Por supuesto, aún tendrá que crear una nueva secuencia para producir la lista de salida.
Creo que debería ser suficiente para mapear un booleano
En código esto es:
boolean isEmpty = anyCollection.stream()
.filter(p -> someFilter(p)) // Add my filter
.map(p -> Boolean.TRUE) // For each element after filter, map to a TRUE
.findAny() // Get any TRUE
.orElse(Boolean.FALSE); // If there is no match return false
-
Si esto es todo lo que necesita, la respuesta de kenglxn es mejor.
-Dominykas Mostauskis
11 de enero de 2020 a las 13:25
-
es inútil, duplica Collection.isEmpty()
– Krzysztof
8 de febrero de 2020 a las 3:30
-
@Krzysiek no es inútil si necesita filtrar la colección. Sin embargo, estoy de acuerdo con Dominykas en que la respuesta de Kenglxn es mejor.
– Hertzu
13 de agosto de 2020 a las 17:56
-
Es porque también duplica
Stream.anyMatch()
– Krzysztof
13 de agosto de 2020 a las 23:02
fénix7360
Siguiendo la idea de Stuart, esto podría hacerse con un Spliterator
como esto:
static <T> Stream<T> defaultIfEmpty(Stream<T> stream, Stream<T> defaultStream) {
final Spliterator<T> spliterator = stream.spliterator();
final AtomicReference<T> reference = new AtomicReference<>();
if (spliterator.tryAdvance(reference::set)) {
return Stream.concat(Stream.of(reference.get()), StreamSupport.stream(spliterator, stream.isParallel()));
} else {
return defaultStream;
}
}
Creo que esto funciona con Streams paralelos como el stream.spliterator()
la operación terminará la secuencia y luego la reconstruirá según sea necesario
En mi caso de uso, necesitaba un valor predeterminado Stream
en lugar de un valor predeterminado. eso es bastante fácil de cambiar si esto no es lo que necesita
-
Si esto es todo lo que necesita, la respuesta de kenglxn es mejor.
-Dominykas Mostauskis
11 de enero de 2020 a las 13:25
-
es inútil, duplica Collection.isEmpty()
– Krzysztof
8 de febrero de 2020 a las 3:30
-
@Krzysiek no es inútil si necesita filtrar la colección. Sin embargo, estoy de acuerdo con Dominykas en que la respuesta de Kenglxn es mejor.
– Hertzu
13 de agosto de 2020 a las 17:56
-
Es porque también duplica
Stream.anyMatch()
– Krzysztof
13 de agosto de 2020 a las 23:02
daniel sp
Simplemente usaría:
stream.count()>0
-
Esto solo funciona si no tiene que procesar la transmisión. Cuando desea procesar los elementos, no funciona, porque después de count () se consume la transmisión. contar es una operación terminal.
– Miguel
04/01/2021 a las 16:00
-
Esto también consume y cuenta innecesariamente el entero corriente, donde un “perezoso” (cortocircuito)
.findAny()
solo procesaría un solo artículo y terminaría.– Alex Shesterov
9 de septiembre de 2021 a las 4:25
-
Cómo esto es mejor que
Optional.isPresent()
? ¡Esto es mucho peor que las otras soluciones!– Diablo
17 de junio a las 7:36
No puedes tener tu pastel y comértelo también, y literalmente, en este contexto. Tienes que consumir el flujo para averiguar si está vacío. Ese es el punto de la semántica de Stream (pereza).
– Marko Topolnik
30 de octubre de 2014 a las 9:21
Eventualmente se consumirá, en este punto debe ocurrir la verificación.
– Cefalópodo
30 de octubre de 2014 a las 9:22
Para verificar que la transmisión no esté vacía, debe intentar consumir al menos un elemento. En ese momento, la corriente ha perdido su “virginidad” y no puede volver a consumirse desde el principio.
– Marko Topolnik
30 de octubre de 2014 a las 9:27
@MarkoTopolnik solo porque es perezoso no significa que, en principio, no pueda amortiguar y volver a emitir el elemento que se miró. Ver flujo de vavr
– Coderino Javarino
24 de enero a las 10:49