Mutable variable capture in anonymous Java classes
August 19, 2013 | 4 min ReadThe Java compiler requires local variables of enclosing contexts referenced in anonymous classes (so-called captured variables) to be final. What if the anonymous class wants to alter the value of the variable, i.e. requires the variable to be mutable? This post shows different ways how to achieve that.
The problem
Let’s assume we have a UI containing a button and a message to be displayed. The message is stored in a local variable. When clicking on the button, the value of the local variable should change:
JButton button = new JButton("Press me!");
String message = "Never been pressed";
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
message = "Pressed";
}
});
The Java compiler will complain with an error like this:
Cannot refer to a non-final variable message inside an inner class defined in a different method
The local variable message
is captured by the actionPerformed
method of the inner class, i.e. it is passed implicitly to the ActionListener
by value. In order to avoid inconsistencies between the values of the “outer” variable and the “inner” variable, the Java compiler requires all captured variables to be final. But if the message
is made final, its value cannot be changed any more by the ActionListener
.
Mutability with an array
The most common way to solve this problem is to put the variable into an array of length one, make the array final and capture the array:
JButton button = new JButton("Press me!");
final String[] message = new String[]{"Never been pressed"};
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
message[0] = "Pressed";
}
});
The value is stored in the first slot of the array. The reference to the array is final but its element stays mutable.
While this approach works fine, it looks strange and will certainly raise questions for developers in your team which have never seen this construct before.
Mutability with a holder
The better way to make the message mutable is to put it into a holder class
class Holder {
private T value;
Holder(T value) {
setValue(value);
}
T getValue() {
return value;
}
void setValue(T value) {
this.value = value;
}
}
and pass the holder into the inner class:
JButton button = new JButton("Press me!");
final Holder mutableMessage = new Holder("Never been pressed");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
mutableMessage.setValue("Pressed");
}
});
This solution states more clearly why the message has been wrapped. If the holder is implemented as a generic utility class, this solution is not more verbose than the one with the array. In case you don’t want to implement the Holder
class yourself, you can also reuse the MutableObject from Apache Commons or the Holder from Google Guava. One could argue that the solution with the array is faster (creating an array is usually faster than instantiating a class), but in most cases the performance loss will be negligible.
Lambda Expressions to the rescue?
Part of the upcoming JDK 8 is Project Lambda - one of its goals is to add support for Closures to the Java programming language. The so-called Lambda expressions offer a way to shorten the declaration of anonymous classes. Additionally I heard rumors that the Java 8 compiler does not require captured variables to be explicitly final, so I had a look at the JDK 8 early access release to see whether it can help me with the code above.
It turns out that Lambda Expressions help with shortening the code of the inner class:
JButton button = new JButton("Press me!");
final Holder messageHolder = new Holder("Never been pressed");
button.addActionListener(e -> messageHolder.setValue("Pressed"));
However even with Lambda Expressions, it is not allowed to change the value of a captured variable. There is a new concept called effectively final which allows you to omit the final keyword - however the compiler still checks that the value of the variable is not changed in the Lambda Expression. Brian Goetz explains in his blog that allowing capture of mutable local variables in a multi-threaded environment would probably do more harm than good, therefore it will be prohibited. So the final keyword can only be omitted if the variable is not changed in the Lambda Expression body.
Conclusion
This post showed two ways to solve the problem of mutable captured variables in anonymous classes. The upcoming Lambda Expressions help to shorten the code of anonymous classes but do not change the requirements of the Java compiler regarding the mutability.