Adding elements to different collections in a single lambda expression

Viewed 558

I possibly use the wrong terms, feel free to correct.

I have a test method which takes a Runnable:

void expectRollback(Runnable r) { .. }

I can call this method like this:

expectRollback(() -> aList.add(x))

Cool, I understand lambdas! This is awesome. Let's be super clever...

expectRollback(() -> aList.add(x) && bList.add(y))

But what? That doesn't compile: 'void' methods cannot return a value. Doesn't the first call also return a value though? What is the difference between the first and the second call?

2 Answers

It's subtle, but I think I've got it.

In JLS 15.27.3 we have:

A lambda expression is congruent with a function type if all of the following are true:

  • ...
  • If the lambda parameters are assumed to have the same types as the function type's parameter types, then:
    • If the function type's result is void, the lambda body is either a statement expression (§14.8) or a void-compatible block.
    • ...
  • ...

Now aList.add(x) is a statement expression, but aList.add(x) && bList.add(y) is not. You can see that without any lambdas involved - just try to use them as statements:

aList.add(x); // Fine
aList.add(x) && bList.add(y); // Error: "not a statement"

If you wrap that expression in a method call, it's fine, because the method call is a statement expression again:

rollback(() -> Boolean.valueOf(aList.add(x) && bList.add(y)));

I'm not actually suggesting you do that - just trying to explain why that would work but your previous attempt didn't.

If we assume that List.add really will return true every time as documented, just use a block lambda:

rollback(() -> { aList.add(x); bList.add(y); });

I'd argue that's clearer anyway as it makes it more obvious that you don't care about the value returned by the first add call.

Basically your first expression is way to invoke a method but ignore the result:

 aList.add(x) // actually returns a boolean but you ignore this result

This is not anything new for lambdas actually, as it has been like this since java appeared:

List<Integer> list ...
list.add(1); // returns boolean but we ignore the result almost always

So why is the result not ignored in your second example? Well because the JLS says so, as seen here. There are 4 types that will work though:

Method Invocations
Assignments
Increment and Decrement expressions
Class Instance Creation expressions

Your last example uses && which is neither of the 4 types described in the JLS, thus a compile time error.

You could create a helper method to add to both lists(or as you did with Boolean.valueOf):

public static boolean helper(int x) {
    boolean result = first.add(x) && second.add(x);
    return result;
}

And use it:

expectRollback(() -> helper(x))
Related