Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Method references called on a local variable

I am in the process of learning Java 8 and I came across something that I find a bit strange.

Consider the following snippet:

private MyDaoClass myDao;  public void storeRelationships(Set<Relationship<ClassA, ClassB>> relationships) {     RelationshipTransformer transformer = new RelationshipTransformerImpl();      myDao.createRelationships(             relationships.stream()             .map((input) -> transformer.transformRelationship(input))             .collect(Collectors.toSet())     ); } 

Basically, I need to map the input set called relationships to a different type in order to conform to the API of the DAO I'm using. For the conversion, I would like to use an existing RelationshipTransformerImpl class that I instantiate as a local variable.

Now, here's my question:

If I was to modify the above code as follows:

public void storeRelationships(Set<Relationship<ClassA, ClassB>> relationships) {     RelationshipTransformer transformer = new RelationshipTransformerImpl();      myDao.createRelationships(             relationships.stream()             .map((input) -> transformer.transformRelationship(input))             .collect(Collectors.toSet())     );      transformer = null;  //setting the value of an effectively final variable } 

I would obviously get a compilation error, since the local variable transformer is no longer "effectively final". However, if replace the lambda with a method reference:

public void storeRelationships(Set<Relationship<ClassA, ClassB>> relationships) {     RelationshipTransformer transformer = new RelationshipTransformerImpl();      myDao.createRelationships(             relationships.stream()             .map(transformer::transformRelationship)             .collect(Collectors.toSet())     );      transformer = null;  //setting the value of an effectively final variable } 

Then I no longer get a compilation error! Why does this happen? I thought the two ways to write the lambda expression should be equivalent, but there's clearly something more going on.

like image 442
Emil D Avatar asked Jan 02 '15 19:01

Emil D


People also ask

How does method reference work in Java 8?

Java provides a new feature called method reference in Java 8. Method reference is used to refer method of functional interface. It is compact and easy form of lambda expression. Each time when you are using lambda expression to just referring a method, you can replace your lambda expression with method reference.

What is the advantage of method reference in Java 8?

That's all about what is method reference is in Java 8 and how you can use it to write clean code in Java 8. The biggest benefit of the method reference or constructor reference is that they make the code even shorter by eliminating lambda expression, which makes the code more readable.

Can we pass arguments in method reference?

Notice that between a static method and a static method reference, instead of the . operator, we use the :: operator, and that we don't pass arguments to the method reference. In general, we don't have to pass arguments to method references. However, arguments are treated depending on the type of method reference.


2 Answers

JLS 15.13.5 may hold the explanation:

The timing of method reference expression evaluation is more complex than that of lambda expressions (§15.27.4). When a method reference expression has an expression (rather than a type) preceding the :: separator, that subexpression is evaluated immediately. The result of evaluation is stored until the method of the corresponding functional interface type is invoked; at that point, the result is used as the target reference for the invocation. This means the expression preceding the :: separator is evaluated only when the program encounters the method reference expression, and is not re-evaluated on subsequent invocations on the functional interface type.

As I understand it, since in your case transformer is the expression preceding the :: separator, it is evaluated just once and stored. Since it doesn't have to be re-evaluated in order to invoke the referenced method, it doesn't matter that transformer is later assigned null.

like image 132
Eran Avatar answered Sep 23 '22 04:09

Eran


Wild guess but to me, here is what happens...

The compiler cannot assert that the created stream is synchronous at all; it sees this as a possible scenario:

  • create stream from relationships argument;
  • reaffect transformer;
  • stream unrolls.

What is generated at compile time is a call site; it is linked only when the stream unrolls.

In your first lambda, you refer to a local variable, but this variable is not part of the call site.

In the second lambda, since you use a method reference, it means the generated call site will have to keep a reference to the method, therefore the class instance holding that method. The fact that it was referred by a local variable which you change afterwards does not matter.

My two cents...

like image 36
fge Avatar answered Sep 20 '22 04:09

fge