I understand why the following is not accepted by the compiler:
class Foo {
public Supplier<String> makeSupplier() {
String str = "hello";
Supplier<String> supp = () -> return str;
// gives the expected compile error because
// str is not effectively final
// (str is a local variable, compile-time error
// as per JLS 15.27.2.)
str = "world";
return supp;
}
}
What puzzles me is that the compiler accepts the following, and that the unit test passes:
class Bar {
private String str = "hello";
public void setStr(String str) {
this.str = str;
}
public Supplier<String> makeSupplier() {
Supplier<String> supp = () -> { return str; };
return supp;
}
@Test
public void Unit_lambdaCapture() {
Supplier<String> supp = makeSupplier();
Assert.assertEquals(supp.get(), "hello");
setStr("foo");
Assert.assertEquals(supp.get(), "foo");
}
}
Why is the above valid and working correctly? Pointers to relevant sections of the JLS are welcome (section 15.27.2. only talks about local variables).
Forcing the variable to be final avoids giving the impression that incrementing start inside the lambda could actually modify the start method parameter.
A non-final local variable or method parameter whose value is never changed after initialization is known as effectively final. It's very useful in the context of the lambda expression. If you remember, prior to Java 8, we cannot use a non-final local variable in an anonymous class.
A lambda expression can't define any new scope as an anonymous inner class does, so we can't declare a local variable with the same which is already declared in the enclosing scope of a lambda expression. Inside lambda expression, we can't assign any value to some local variable declared outside the lambda expression.
Yes, but instance variables can be referenced and assigned in a lambda, which is surprising to me. Only local variables have the final limitation.
We all agree that the first example won't work as local variables or parameters must be final or effectively final to be used within a lambda expression body.
But your second example does not involve local variables or parameters, as str
is an instance field. Lambda expressions can accessing instance fields the same way as instance methods:
15.27.2. Lambda Body
A lambda body is either a single expression or a block (§14.2). Like a method body, a lambda body describes code that will be executed whenever an invocation occurs.
In fact, the java compiler creates a private method lambda$0
out of your lambda expression, that simply accesses the instance field str
:
private java.lang.String lambda$0() {
0 aload_0; /* this */
1 getfield 14; /* .str */
4 areturn;
}
Another point of view: You could also implement the Supplier
using a plain-old anonymous inner class:
public Supplier<String> makeSupplier() {
return new Supplier<String>() {
public String get() { return str; }
};
}
Accessing instance fields from inner classes is very common and not a speciality of Java 8.
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