1

This question is similar to Lambdas: local variables need final, instance variables don't,but the only difference is this question is valid even without lambda expressions i.e. valid even on Java7.

Here is the code snippet below.

public class MyClass {

    Integer globalInteger = new Integer(1);

    public void someMethod() {

        Integer localInt = new Integer(2);

        Runnable runnable = new Runnable() {

            @Override
            public void run() {

                globalInteger = new Integer(11);//no error
                localInt =  new Integer(22);//error here

            }
        };      
    }
}

I am allowed to reassign globalInteger a new value but not to localInteger. Why is this difference?

4 Answers4

3

To understand why non-local variables are allowed to change, we first need to understand why local variables aren't. And that's because local variables are stored on the stack (which instance (or static) variables aren't).

The problem with stack variables is that they're going to disappear once their containing method returns. However the instance of your anonymous class might live longer than that. So if accessing local variables were implemented naively, using the local variable from inside the inner class after the method returned would access a variable on a stack frame that no longer exists. That would either lead to a crash, an exception or undefined behavior depending on the exact implementation. Since that's clearly bad, access to local variables is implemented via copying instead. That is, all the local variables that are used by the class (including the special variable this) are copied into the anonymous object. So when a method of the inner class accesses a local variable x, it's not actually accessing that local variable. It's accessing a copy of it stored inside the object.

But what would happen if a local variable changed after the object was created or if a method of the object changed the variable? Well, the former would cause the local variable to change, but not the copy in the object, and the latter would change the copy, but not the original. So either way the two versions of the variable would no longer be the same, which would be very counter-intuitive to any programmer who doesn't know about the copying going on. So to avoid this problem, you're only allowed to access local variables if their value is never changed.

Instance variables don't need to be copied because they won't disappear until their containing object is garbage collected (and static variables never disappear) - since the anonymous object will contain a reference to the outer this, this won't happen until the anonymous object is garbage collected as well. So since they aren't copied, modifying them doesn't cause any issues and there's no reason to disallow it.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
1

Because JVM has no machine instruction to assign a variable located at any stack frame which is different from the current stack frame.

Alexei Kaigorodov
  • 13,189
  • 1
  • 21
  • 38
0

because the lambda function is not part of the class.

Think about the following code (small changes of yours):

public Runnable func() {
    Integer localInt = new Integer(2);
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            globalInteger = new Integer(11);//no error
            localInt =  new Integer(22);//error here
        }
    };
    return runnable;
}

//Somewhere in the code:
Runnable r = func();
r.run(); // At this point localInt is not defined.
Roee Gavirel
  • 18,955
  • 12
  • 67
  • 94
  • 1
    This explanation skims over the fact that using `localInt` in a way that doesn't modify it would be perfectly legal. If it's not defined, then reading it would be just as bad as writing it, no? – sepp2k Dec 09 '18 at 13:59
  • @sepp2k - Not exactly, that's related to how the caption of a lambda works. It allows you to read local variables which are `Effectively final` that means that you can't change them inside or outside of the lambda function. Then when the lambda function is created it actually copy the value to the veriable to the lambda function, later you actually read the copy not the original parameter. For more information you can read: http://javahabit.com/2016/06/16/understanding-java-8-lambda-final-finally-variable/ – Roee Gavirel Dec 09 '18 at 14:07
0

The complier tells you the error that variable in inner class must be final or effectively final. This is due to Java's 8 closure and how JVM captures the reference. The restriction is that the reference captured in the lambda body must be final (not re-assignable), and complier needs to ensure it doesn't reference copies of local variables.

so if you access instance variable, your lambda is really referencing this instance of the surrounding class, which is effective final (non-changing reference). In addition, if you use a wrapper class or array, the complier error also goes away.

夢のの夢
  • 5,054
  • 7
  • 33
  • 63