7

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.

slartidan
  • 20,403
  • 15
  • 83
  • 131
  • 2
    Related: http://stackoverflow.com/questions/26649062/how-to-check-if-a-java-8-stream-is-empty – Tunaki Feb 23 '16 at 14:49

5 Answers5

3

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.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
Boris the Spider
  • 59,842
  • 6
  • 106
  • 166
  • When using `AtomicBoolean` I would prefer to use `peek` - but this is only a personal preference. Thanks for all the *workarounds* you proposed - I chose the second one for my current project. Thanks! – slartidan Feb 23 '16 at 15:06
  • @Boris Did you have a reason to chose the non-short-circuiting `|` rather than `||` in the reduce operator? I don't think it will work differently, but I use `|` more rarely and was wondering if there was a difference in behavior I missed. – Joshua Goldberg Jul 30 '21 at 15:49
  • @Joshua what is there to short circuit? – Boris the Spider Jul 30 '21 at 21:32
  • Like I noted, I think the behavior is equivalent either way and wanted to make sure. – Joshua Goldberg Jul 31 '21 at 22:42
2

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.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
  • To fix the `null` first element issue you could change the reduce to e.g. `reduce((a, b) -> true)`. – Didier L Feb 23 '16 at 16:28
  • @DidierL, true, that works for stream of Objects. If you have stream of something else, you may need to perform unsafe cast first (or add something like `.map(Object.class::cast)`). – Tagir Valeev Feb 23 '16 at 16:30
  • Now imagine an implementation being smart enough to recognize that `.reduce((a, b) -> a)` is semantically the same as “get first element” and short-cutting the operation… – Holger Feb 24 '16 at 09:57
  • @Holger, I would happily delete my answer when Java becomes smart enough to recognize such thing. Unfortunately we cannot hope so in the observable future... – Tagir Valeev Feb 24 '16 at 10:04
  • The key point is, the execution of `peek` is an artifact of the implementation dependent stream processing and the problems of `peek(…).count()` with Java 9 only *one* example of how relying on assumptions about `peek` can break your code in the future. And you might be right that such an optimization is far away, but exactly that makes me doubt that you will remember this answer and delete it *then*. I think any `peek` based solution should at least have a disclaimer about its possible short-term nature. – Holger Feb 24 '16 at 10:15
1

There’s a simple straight-forward solution:

Spliterator<Object> sp=stream.spliterator();
if(!sp.tryAdvance(itemConsumer))
    emptyAction.run();
else
    sp.forEachRemaining(itemConsumer);

You can even keep parallel support for the elements after the first, if you wish:

Spliterator<Object> sp=stream.parallel().spliterator();
if(!sp.tryAdvance(itemConsumer))
    emptyAction.run();
else
    StreamSupport.stream(sp, true).forEach(itemConsumer);

In my opinion, it is much easier to understand as a reduce based solution.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • 1
    Parallel version has problems here. In Java-8, if you have upstream `flatMap` operation, you may have out-of-order elements in the output (see [my question](http://stackoverflow.com/q/31152557/4856258)). It's probably ok if order is not required, but in Java-9 `trySplit()` is totally disabled after `tryAdvance()` when there are any upstream intermediate ops. See [this discussion](http://mail.openjdk.java.net/pipermail/core-libs-dev/2016-February/038511.html). – Tagir Valeev Feb 24 '16 at 03:57
  • @Tagir Valeev: now that you mention it, I remember that question and as I stated in my answer there, I had to deal with this as well in my own implementation. Though, my implementation got a straight-forward fix, now returning the correct order rather than disabling splitting. Well my `flatMap` also is lazy, so there seem to be fundamental differences. Anyway, the solution above shouldn’t be blamed for bugs and flaws of a particular stream implementation. – Holger Feb 24 '16 at 09:48
1

You could do this:

if(stream.peek(itemConsumer).count() == 0){
    emptyAction.run();
}

But it seems that count may be changed to skip the peek if it knows the size of the Stream in Java 9 (see here), so if you want it to work in the future you could use:

if(stream.peek(itemConsumer).mapToLong(e -> 1).sum() == 0){
    emptyAction.run();
}
Alex - GlassEditor.com
  • 14,957
  • 5
  • 49
  • 49
  • It’s a general problem of all `peek` based solutions that they abuse the API. `peek` is merely for debugging the processing and if there is way to solve the task without processing elements, they may be skipped. So in theory, an implementation could be smart enough to optimize `.mapToLong(e -> 1).sum()` to do the same as `count()`. Or even smarter to recognize that the follow-up comparison with `==0` implies that you only need to know whether there is at least one element. Such optimizations do not exist today but their absence is not a guaranteed behavior. – Holger Feb 24 '16 at 09:54
0

Another attempt to use reduce:

    Stream<Object> stream = Stream.of("a","b","c");
    //Stream<Object> stream = Stream.empty();


    Runnable defaultRunnable = () -> System.out.println("empty Stream");
    Consumer<Object> printConsumer = System.out::println;

    Runnable runnable = stream.map(x -> toRunnable(x, printConsumer)).reduce((a, b) -> () -> {
        a.run();
        b.run();
    }).orElse(defaultRunnable);

    runnable.run(); // prints a, b, c (or empty stream when it is empty)


    // for type inference
    static <T> Runnable toRunnable(T t, Consumer<T> cons){ 
        return ()->cons.accept(t);
    }

This approach does not use peek() which according to Javadoc "mainly exists to support debugging"

user140547
  • 7,750
  • 3
  • 28
  • 80