Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Special behavior of a stream if there are no elements

How can I express this with java8 streaming-API?

I want to perform itemConsumer for every item of a stream. If there are no items I want to perform emptyAction.

Of course I could write something like this:

Consumer<Object> itemConsumer = System.out::println;
Runnable emptyAction = () -> {System.out.println("no elements");};

Stream<Object> stream = Stream.of("a","b"); // or Stream.empty()
List<Object> list = stream.collect(Collectors.toList());
if (list.isEmpty())
    emptyAction.run();
else
    list.stream().forEach(itemConsumer);

But I would prefer to avoid any Lists.

I also thought about setting a flag in a peek method - but that flag would be non-final and therefore not allowed. Using a boolean container also seems to be too much of a workaround.

like image 407
slartidan Avatar asked Feb 23 '16 14:02

slartidan


People also ask

What happens when you stream an empty list?

toList()) , you'll always get an output List (you'll never get null ). If the Stream is empty (and it doesn't matter if it's empty due to the source of the stream being empty, or due to all the elements of the stream being filtered out prior to the terminal operation), the output List will be empty too.

What is use of empty stream in Java?

Return Value : Stream empty() returns an empty sequential stream. Note : An empty stream might be useful to avoid null pointer exceptions while callings methods with stream parameters.

Does a stream store its elements?

Getting Started with Streams Sequence of elements: A stream provides an interface to a sequenced set of values of a specific element type. However, streams don't actually store elements; they are computed on demand. Source: Streams consume from a data-providing source such as collections, arrays, or I/O resources.

How do you know if an optional list is empty?

The isEmpty() method of List interface in java is used to check if a list is empty or not. It returns true if the list contains no elements otherwise it returns false if the list contains any element.


2 Answers

You could coerce reduce to do this. The logic would be to reduce on false, setting the value to true if any useful data is encountered.

The the result of the reduce is then false then no items have been encountered. If any items were encountered then the result would be true:

boolean hasItems = stream.reduce(false, (o, i) -> {
    itemConsumer.accept(i);
    return true;
}, (l, r) -> l | r);
if (!hasItems) {
    emptyAction.run();
}

This should work fine for parallel streams, as any stream encountering an item would set the value to true.

I'm not sure, however, that I like this as it's a slightly obtuse use of the reduce operation.

An alternative would be to use AtomicBoolean as a mutable boolean container:

final AtomicBoolean hasItems = new AtomicBoolean(false);
stream.forEach(i -> {
    itemConsumer.accept(i);
    hasItems.set(true);
});
if (!hasItems.get()) {
    emptyAction.run();
}

I don't know if I like that more or less however.

Finally, you could have your itemConsumer remember state:

class ItemConsumer implements Consumer<Object> {

    private volatile boolean hasConsumedAny;

    @Override
    public void accept(Object o) {
        hasConsumedAny = true;
        //magic magic
    }

    public boolean isHasConsumedAny() {
        return hasConsumedAny;
    }
}

final ItemConsumer itemConsumer = new ItemConsumer();
stream.forEach(itemConsumer::accept);
if (!itemConsumer.isHasConsumedAny()) {
    emptyAction.run();
}

This seems a bit neater, but might not be practical. So maybe a decorator pattern -

class ItemConsumer<T> implements Consumer<T> {

    private volatile boolean hasConsumedAny;
    private final Consumer<T> delegate;

    ItemConsumer(final Consumer<T> delegate) {
        this.delegate = delegate;
    }

    @Override
    public void accept(T t) {
        hasConsumedAny = true;
        delegate.accept(t);
    }

    public boolean isHasConsumedAny() {
        return hasConsumedAny;
    }
}

final ItemConsumer<Object> consumer = new ItemConsumer<Object>(() -> /** magic **/);

TL;DR: something has to remember whether you encountered anything during the consumption of the Stream, be it:

  • the Stream itself in case of reduce;
  • AtomicBoolean; or
  • the consumer

I think the consumer is probably best placed, from a logic point of view.

like image 185
Boris the Spider Avatar answered Nov 04 '22 09:11

Boris the Spider


A solution without any additional variables:

stream.peek(itemConsumer).reduce((a, b) -> a).orElseGet(() -> {
    emptyAction.run();
    return null;
});

Note that if the stream is parallel, then itemConsumer could be called simultaneously for different elements in different threads (like in forEach, not in forEachOrdered). Also this solution will fail if the first stream element is null.

like image 34
Tagir Valeev Avatar answered Nov 04 '22 10:11

Tagir Valeev