This article is the first one of a serie dedicated to Java 8 Lambda expressions. The purpose is not to introduce the main concepts of lambda expressions, many blogs over there do it very well. The idea is to
shed some light on tricky details and side effects. If you’re not familiar with the new lambda-related features in Java 8, I strongly advise to look at the following article http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html
Today we’ll look at the mutability issue when dealing with closures.
DISCLAIMER: all the details exposed in this post were observed related to the JDK 8 demo version as of July 10th 2012. Since the JDK is still in beta, some assertions may not hold in future
Please note that all the example code in this post can be found on my GitHub repository https://github.com/doanduyhai/Java8_Lambda_In_Details
I Abstract
When dealing with closures, we should be aware of the fact that the closure code body can have reference on objects of its enclosing class. This leads to 2 major implications:
- The enclosing object instance referred to by the closure code (with the this keyword or any implicit unqualified reference to its fields) cannot be garbaged by the GC even though it hasn’t any direct reference left in the heap
- If the referred object is mutable, we may encounters some side effects
In the rest of this post, we will focus on the second point.
II Capture of variable in closure
Let’s consider the following code
public interface VariableCaptureInClosureSAM { MutableObject retrieveMutable(); } public class MutableObject { private String content = "default_content"; public String getContent() { return content; } public void setContent(String content) { this.content = content; } } public class VariableCaptureInClosure { final public MutableObject mutable = new MutableObject(); public VariableCaptureInClosureSAM createClosure() { VariableCaptureInClosureSAM lambda = ()-> { return this.mutable; }; return lambda; } public void addToMutableContent(String toAdd) { this.mutable.setContent(this.mutable.getContent() + " " + toAdd); } public void printMutableContent(String stage) { System.out.println("nCurrent VariableCaptureInClosure.mutable reference : " + this.mutable.toString()); System.out.println("At stage " + stage + ", current VariableCaptureInClosure.mutable content = " + this.mutable.getContent()); } public static void main(String[] args) { VariableCaptureInClosure variableCapture = new VariableCaptureInClosure(); VariableCaptureInClosureSAM lambda = variableCapture.createClosure(); MutableObject mutableFromClosure = lambda.retrieveMutable(); System.out.println("nmutableFromClosureBeforeMutation reference : " + mutableFromClosure.toString()); System.out.println("mutableFromClosureBeforeMutation content = " + mutableFromClosure.getContent()); variableCapture.printMutableContent("BEFORE MUTATION"); variableCapture.addToMutableContent("mutable"); variableCapture.printMutableContent("AFTER MUTATION"); System.out.println("nmutableFromClosureAfterMutation reference : " + mutableFromClosure.toString()); System.out.println("mutableFromClosureAfterMutation content = " + mutableFromClosure.getContent()); System.out.println(""); } }
First we declare a SAM (Single Method Interface) with the abstract MutableObject retrieveMutable() method.
The MutableObject is a plain POJO with a String field and a setter to change its value and hence make it mutable. Please note that this MutableObject is initialized with “default_content” as content.
In the VariableCaptureInClosure class, we define the method createClosure() whose purpose is to return a lambda expression that captures the final mutable field (line 25)
In the main() method, we start by retrieving the lambda expression (line 50) then we get a reference to the mutable object (line 52)
We then display the reference on this mutable object and its content:
mutableFromClosureBeforeMutation reference : fr.doan.lambda.mutability.MutableObject@8c5a4f
mutableFromClosureBeforeMutation content = default_content
Next, we get the reference of the mutable object from the VariableCaptureInClosure instance and display its content (lines 53-54):
Current VariableCaptureInClosure.mutable reference : fr.doan.lambda.mutability.MutableObject@8c5a4f
At stage BEFORE MUTATION, current VariableCaptureInClosure.mutable content = default_content
At this stage, we can clearly see that both instances of MutableObject class, the one returned by the lambda expression and the one of the VariableCaptureInClosure instance, are identical, not surprising.
Now the idea is to change the content of this mutable object by calling the method addToMutableContent(String toAdd) and print out the result.
Current VariableCaptureInClosure.mutable reference : fr.doan.lambda.mutability.MutableObject@8c5a4f
At stage AFTER MUTATION, current VariableCaptureInClosure.mutable content = default_content mutable
The content has been actually changed. Now if we check again the MutableObject instance from the closure, its content has been also changed and it’s expected.
mutableFromClosureAfterMutation reference : fr.doan.lambda.mutability.MutableObject@8c5a4f
mutableFromClosureAfterMutation content = default_content mutable
This small example shows us that a lambda expression can keep a reference on a object from its enclosing class (but it’s not exactly that, we’ll see later in the next chapter). If the object is mutable, changes can come both from the owning class and the lambda expression.
The new collection API enhancement in Java 8 provides some usefull method to work in parallel on collection. You should take care about your object mutability if you are working on an multi-threaded environment since it may lead to serious side effects.
Please notice that the mutable field being declared as final (line 23) does not help it being less mutable since mutability and final variable are two distinct concepts (most people are still confused about it)
The above code example can be found on GitHub at https://github.com/doanduyhai/Java8_Lambda_In_Details. Just execute the VariableCaptureInClosure.bat(VariableCaptureInClosure.sh) script
III Non final variable
In the following example, we make the previous mutable field non final.
public class NonFinalVariableCaptureInClosure { public MutableObject mutable = new MutableObject(); public VariableCaptureInClosureSAM createClosure() { VariableCaptureInClosureSAM lambda = ()-> { return this.mutable; }; return lambda; } public void addToMutableContent(String toAdd) { this.mutable.setContent(this.mutable.getContent() + " " + toAdd); } public void printMutableContent(String stage) { System.out.println("nCurrent VariableCaptureInClosure.mutable reference : " + this.mutable.toString()); System.out.println("At stage " + stage + ", current VariableCaptureInClosure.mutable content = " + this.mutable.getContent()); } public static void main(String[] args) { NonFinalVariableCaptureInClosure variableCapture = new NonFinalVariableCaptureInClosure(); VariableCaptureInClosureSAM lambda = variableCapture.createClosure(); MutableObject mutableFromClosureBeforeRefChange = lambda.retrieveMutable(); System.out.println("nmutableFromClosureBeforeRefChange reference : " + mutableFromClosureBeforeRefChange.toString()); System.out.println("mutableFromClosureBeforeRefChange content = " + mutableFromClosureBeforeRefChange.getContent()); MutableObject newMutable = new MutableObject(); newMutable.setContent("new_content"); variableCapture.mutable = newMutable; MutableObject mutableFromClosureAfterRefChange = lambda.retrieveMutable(); System.out.println("nmutableFromClosureAfterRefChange reference : " + mutableFromClosureAfterRefChange.toString()); System.out.println("mutableFromClosureAfterRefChange content = " + mutableFromClosureAfterRefChange.getContent()); System.out.println(""); } }
The code is quite straightforward. We create a new MutableObject and assign it to the mutable field of the NonFinalVariableCaptureInClosure instance (lines 35-37). The output shows:
mutableFromClosureBeforeRefChange reference : fr.doan.lambda.mutability.MutableObject@14f7121
mutableFromClosureBeforeRefChange content = default_contentmutableFromClosureAfterRefChange reference : fr.doan.lambda.mutability.MutableObject@8c5a4f
mutableFromClosureAfterRefChange content = new_content
This result is more surprising. We expect that the second call to lambda.retrieveMutable() (line 39) will return us the old MutableObject instance (14f7121) but it’s not the case.
What happends under the hood is that the lambda expression captures its enclosing context by keeping a reference on the NonFinalVariableCaptureInClosure instance via the this keyword in return this.mutable; (line 9) and not a reference to the MutableObject itself…
So the second call to lambda.retrieveMutable() returns the new instance of MutableObject we just create and assign to the NonFinalVariableCaptureInClosure object.
The above code example can be found on GitHub at https://github.com/doanduyhai/Java8_Lambda_In_Details. Just execute the NonFinalVariableCaptureInClosure.bat(NonFinalVariableCaptureInClosure.sh) script
To be continued …
Have you seen this post?
It is a Closure implementation for Java 5, 6 and 7, it looks pretty neat, and works really well.
http://mseifed.blogspot.se/2012/09/closure-implementation-for-java-5-6-and.html