The consensus appears to be that this is because when you do it using an anonymous class, one
refers to a variable, whereas when you do it using a method reference, the value of one
is captured when the method handle is created. In fact, I think that in both cases one
is a value rather than a variable. Let's consider anonymous classes, lambda expressions and method references in a bit more detail.
Anonymous classes
Consider the following example:
static Supplier<String> getStringSupplier() {
final Object o = new Object();
return new Supplier<String>() {
@Override
public String get() {
return o.toString();
}
};
}
public static void main(String[] args) {
Supplier<String> supplier = getStringSupplier();
System.out.println(supplier.get()); // Use o after the getStringSupplier method returned.
}
In this example, we are calling toString
on o
after the method getStringSupplier
has returned, so when it appears in the get
method, o
cannot refer to a local variable of the getStringSupplier
method. In fact it is essentially equivalent to this:
static Supplier<String> getStringSupplier() {
final Object o = new Object();
return new StringSupplier(o);
}
private static class StringSupplier implements Supplier<String> {
private final Object o;
StringSupplier(Object o) {
this.o = o;
}
@Override
public String get() {
return o.toString();
}
}
Anonymous classes make it look as if you are using local variables, when in fact the values of these variables are captured.
In contrast to this, if a method of an anonymous class references the fields of the enclosing instance, the values of these fields are not captured, and the instance of the anonymous class does not hold references to them; instead the anonymous class holds a reference to the enclosing instance and can access its fields (either directly or via synthetic accessors, depending on the visibility). One advantage is that an extra reference to just one object, rather than several, is required.
Lambda expressions
Lambda expressions also close over values, not variables. The reason given by Brian Goetz here is that
idioms like this:
int sum = 0;
list.forEach(e -> { sum += e.size(); }); // ERROR
are fundamentally serial; it is quite difficult to write lambda bodies
like this that do not have race conditions. Unless we are willing to
enforce -- preferably at compile time -- that such a function cannot
escape its capturing thread, this feature may well cause more trouble
than it solves.
Method references
The fact that method references capture the value of the variable when the method handle is created is easy to check.
For example, the following code prints "a"
twice:
String s = "a";
Supplier<String> supplier = s::toString;
System.out.println(supplier.get());
s = "b";
System.out.println(supplier.get());
Summary
So in summary, lambda expressions and method references close over values, not variables. Anonymous classes also close over values in the case of local variables. In the case of fields, the situation is more complicated, but the behaviour is essentially the same as capturing the values because the fields must be effectively final.
In view of this, the question is, why do the rules that apply to anonymous classes and lambda expressions not apply to method references, i.e. why are you allowed to write o::toString
when o
is not effectively final? I do not know the answer to that, but it does seem to me to be an inconsistency. I guess it's because you can't do as much harm with a method reference; examples like the one quoted above for lambda expressions do not apply.