Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Stream anyMatch() goes through the whole stream

Given three functions like this:

private Optional<Integer> abc() {
    return Optional.of(6);
}


private Optional<Integer> def() {
    return Optional.of(3);
}


private Optional<Integer> ghi() {
    return Optional.of(9);
}

If I want to check if one of the three functions return something which is greater than 5 (of course wrapped in Optional), in a traditional imperative style I would do it like this:

if( abc().get() > 5 || def().get() > 5 || ghi().get() > 5) {
  ......// Do something
 }  // I am not doing get() without checking ifPresent() just for simplicity sake

This would only go into function abc() and skip def() and ghi(), because the first expression returns true. Which is a good optimization. Now if I write the same in a functional style using Streams,

if( Stream.of(abc(), def(), ghi()).anyMatch(integer -> integer.get() > 5)) {
   .........
}

I thought the same would happen, i.e. only abc() will be called. But it calls all three functions. Isn't it redundant to check other two functions when there is anyMatch()?

It is same in the case of noneMatch(); the flow goes through whole Stream. I am just wondering: Isn't it really a bad thing to traverse the whole stream (especially if the stream has many values), even if the condition is met at the first element?

like image 927
pvpkiran Avatar asked Jan 28 '23 19:01

pvpkiran


1 Answers

This is because the Stream#of happens before Stream#anyMatch, so all of the methods are called since they happens before Stream#of.

You can make Stream#anyMatch happens before actual method invocation by using Supplier<Optional<Integer>>, for example:

// Note: it just create Suppliers and actual method is called on demand 
Stream<Supplier<Optional<Integer>>> values=Stream.of(this::abc,this::def,this::ghi);

if(values.anyMatch(integer -> integer.get().get() > 5)) {
    .........
}

As @FedericoPeraltaSchaffner already mentioned, the Optional maybe empty you can just use Optional#orElse(0) instead of Optional#get, or use Opitional#filter(it -> it > 5).isPresent().

Edit

To illustrate the short-circuiting terminal operations of Stream, you should use lambdas/method reference expressions since method call happens before Stream#of, for example:

Supplier<Optional<Integer>> failsOnMismatched = () -> { 
   throw new IllegalStateException(); 
};

// the instantiation of method reference happen before `Stream#of`,
// but the linked method is called on demand.
//                 v 
if(Stream.of(this::abc, failsOnMismatched).anyMatch(it -> it.get().orElse(0) > 5)){
  //reached
}

//it is failed since the value of def() <= 5 ---v
if(Stream.of(this::def, failsOnMismatched).anyMatch(it -> it.get().orElse(0) > 5)){
  //unreachable
}
like image 88
holi-java Avatar answered Feb 11 '23 14:02

holi-java