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.
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.
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.
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.
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.
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:
relationships
argument;transformer
;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...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With