Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 431
zedoo Avatar asked Oct 01 '17 08:10

zedoo


People also ask

Can lambda expressions contain data types?

The lambda expressions have a very simple, precise syntax and provide flexibility to specify the datatypes for the function parameters. Its return type is a parameter -> expression body to understand the syntax, we can divide it into three parts.

Can lambda have more than one statement?

Statement lambdasThe body of a statement lambda can consist of any number of statements; however, in practice there are typically no more than two or three.

What is lambda comparator?

A lambda expression used with any functional interface and Comparator is a functional interface. The Comparator interface has used when sorting a collection of objects compared with each other. In the below example, we can sort the employee list by name using the Comparator interface.


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.

like image 162
Jon Skeet Avatar answered Oct 13 '22 19:10

Jon Skeet


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))
like image 37
Eugene Avatar answered Oct 13 '22 19:10

Eugene