2

Are || and Stream.of(..).anyMatch(e-> e==true) functionally equivalent when applied to the same series of conditions in the same order?

Task: Run a quick test on a series of conditions to determine whether any are true.

Possible solutions: - Separate each condition with an ‘or’ symbol (||) - Include each condition in a Stream.of(..) statement that is appended with .anyMatch(e-> e==true)

The documentation for anyMatch(..) states, “Returns whether any elements of this stream match the provided predicate. May not evaluate the predicate on all elements if not necessary for determining the result [emphasis added].”

Based on this statement, my understanding is that the two solutions indicated above are functionally the same, so that if the second element in a serious of four is the first element that is true, then elements three and four won’t be evaluated.

In practice, however, this seems not to be true. Consider the following where item is null, empty is true and UtilMethods.isEmpty(..) is a custom library method that tests if a given parameter is null or an empty String (""):

@Override
protected void updateItem(Pair<K, V> item, boolean empty) {
    super.updateItem(item, empty);

    boolean test1 = empty
             || Objects.isNull(item)
             || UtilMethods.isEmpty(item.toString())
             || Objects.isNull(item.getValue());

    boolean test2 = Stream.of(
        empty,
        isNull(item),
        UtilMethods.isEmpty(item.toString()),
        isNull(item.getValue()))
    .anyMatch(e -> e == true);
}

The || code runs as expected. However, the Stream.of(..).anyMatch(..) throws a NullPointerException when the third element is reached because item is null.

This doesn’t make sense in view of the documentation for anyMatch(..) that is referenced above, which suggests that the third element shouldn’t even be reached. Or am I missing something here?

Thanks

jfr
  • 397
  • 2
  • 17
  • Why? Asking because I don’t see why and how you would want to exploit this functional equivalent, and because I and we would like to guide you with your real problem if we can. – Ole V.V. Mar 15 '20 at 11:20
  • A detail, `.anyMatch(e -> e)` suffices and is usually preferred. – Ole V.V. Mar 15 '20 at 11:21
  • @Ole V.V - Thanks for your comments. About `.anyMatch(e->e)`, that looks indeed better and will be used going forward. I ran into some problems a while back where I used code that was similar (and used in a different context), so as a matter of 'personal good practice' started using `e==true` just to be clear. About the reason for the question, I'm working on a library project that I'm using also as an opportunity to become more fluent with the Stream API, so I'm trying it wherever I possibly can as a self-teaching method. Other replies to this question have taught a valuable lesson. Thanks. – jfr Mar 15 '20 at 11:52

3 Answers3

5

UtilMethods.isEmpty(item.toString()) is evaluated before Stream.of() is executed, so it will throw a NullPointerException regardless of whether or not you call anyMatch afterwards.

Stream.of(), just as any method call, evaluates all of its arguments before being executed, so it must evaluate UtilMethods.isEmpty(item.toString()).

You can see the same behavior in a much simpler snippet:

String s = null;
Stream<Integer> stream = Stream.of (5,s.length());

Hence, the documentation of anyMatch is irrelevant to the behavior you observed.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • Thanks for your reply, it's spot on like the others. The key point is to identify the points where the code effectively runs. I was assuming the VM would start with `anyMatch(..)` and then turn to the stream, ... and that's not what happens as you and the others have pointed out. – jfr Mar 15 '20 at 12:04
4

Your observation is correct. The two code snippets are indeed not the same. The important behaviour that || does, that Stream does not, is that || is short circuiting. When the first operand is true, || stops evaluating immediately. This is not true for the stream operation you have written. All 4 expressions:

empty,
isNull(item),
UtilMethods.isEmpty(item.toString()),
isNull(item.getValue())

Are evaluated before the stream even runs. Yes, anyMatch is lazy, but of is not. See what I mean? anyMatch will not evaluate e == true for every element if a previous element evaluated to true already. This does not apply to of.

To replicate the || behaviour, you'd have to wrap those 4 expressions into Supplier<Boolean>s, and evaluate them in anyMatch:

boolean test2 = Stream.<Supplier<Boolean>>of(
    () -> empty,
    () -> isNull(item),
    () -> UtilMethods.isEmpty(item.toString()),
    () -> isNull(item.getValue()))
    .anyMatch(Supplier::get);
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Thanks for your reply, like the others it's spot on. No offense but Eran deserves the points for being first :). – jfr Mar 15 '20 at 11:56
  • @jfr I think Sweeper has enough points to not feel bad :) – Kayaman Mar 15 '20 at 12:32
  • @Kayaman - Indeed, in terms of my reputation points compared to everyone who replied here I feel sort of like Trump when his real IQ is measured against the rest of the world - he's in single digits and everyone else is operating over 100. – jfr Mar 15 '20 at 13:01
3

As you may long know, || uses shortcut evaluation, that is, if the first item it true, the second is never evaluated. This fact saves you from a NullPointerException in hte first case.

A stream pipeline has a similar behaviour: the final anyMatch only pulls enough elements from the stream to determine whether there is a match. So it may surprise that you get the exception. The explanation of Eran is correct, though: All the arguments of Stream.of() are evaluated before of is called to create the stream. This causes the exception. In other words, the stream never gets created. So the evaluation order in the stream never gets relevant in this case.

That this must be so is probably clearer to see if for a moment we forget that this is a stream operation and just look at it as Java method calls. The structure of the calls in your stream code is similar to the following, only I have simplified it a bit:

SomeClass.foo(ref.bar()).baz();

The sunshine order of evaluation of this expression is dictated by Java:

  1. ref.bar() is evaluated to get the argument to pass to foo();
  2. foo() is called;
  3. baz() is called on the object returned from foo().

However if ref is null, the first step throws a NullPointerException and steps 2 and 3 are never performed. So there is nothing foo() nor baz() can do to change the order of evaluation nor to prevent the exception from happening. Similarly in your code there is nothing the stream pipeline can do to prevent all arguments to Stream.of() to be evaluated before of() is called to create the stream.

The rules about the order of evaluation were laid down in Java long before streams were introduced in the library and are similar to other programming languages. They wisely did not change them radically when introducing streams in Java 8. Sweeper in his answer shows a nice way to obtain the evaluation order you had expected.

Shortcut evaluation is explained in many places. One is here: What is the difference between the | and || or operators?

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • Thanks for your reply and also your queries that are appended to my question. All the answers are spot on and helpful not just for the question I asked, but also to point out how the process here doesn't start from the 'outside' with `anyMatch(..)` but instead from the 'inside' with each element in `Stream.of(..)`. Easy to overlook until you get bit, ... like here. – jfr Mar 15 '20 at 12:00