Adding elements to different collections in a single lambda expression

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?

Jon Skeet
people
quotationmark

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.

people

See more on this question at Stackoverflow