Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do java 8 lambdas allow access to non-final class variables? [duplicate]

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).

like image 654
felixx Avatar asked Mar 13 '15 10:03

felixx


People also ask

Why do variables in lambda need to be final?

Forcing the variable to be final avoids giving the impression that incrementing start inside the lambda could actually modify the start method parameter.

Can we use Non final variable in lambda?

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.

Can you pass local variables to lambda expressions and why you can or you Cannot?

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.

Can we use instance variable in 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.


1 Answers

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.

like image 189
isnot2bad Avatar answered Oct 25 '22 19:10

isnot2bad