Search code examples
javajava-8java-stream

How to check if a Java 8 Stream is empty?


How can I check if a Stream is empty and throw an exception if it's not, as a non-terminal operation?

Basically, I'm looking for something equivalent to the code below, but without materializing the stream in-between. In particular, the check should not occur before the stream is actually consumed by a terminal operation.

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();
}

Solution

  • If you can live with limited parallel capablilities, the following solution will work:

    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);
    }
    

    Here is some example code using it:

    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);
    

    The problem with (efficient) parallel execution is that supporting splitting of the Spliterator requires a thread-safe way to notice whether either of the fragments has seen any value in a thread-safe manner. Then the last of the fragments executing tryAdvance has to realize that it is the last one (and it also couldn’t advance) to throw the appropriate exception. So I didn’t add support for splitting here.