简体   繁体   中英

Why a non-final “local” variable cannot be used inside an inner class, and instead a non-final field of the enclosing class can?

There are some topics on Stack Overflow on the compiler error Cannot refer to a non-final variable message inside an inner class defined in a different method and the solution is "declare it as final and you're done", but with this theoretical question I would like to inspect what is the logical reason why this code cannot compile:

private void updateStatus(String message) {
    Runnable doUpdateStatus = new Runnable() {
         public void run() {
             /* do something with message */
         }
    }
    /* do something with doUpdateStatus, like SwingUtilities.invokeLater() */
}

(solution: declare message as final) whereas this one does:

private String enclosingClassField;
private void updateStatus() {
    Runnable doUpdateStatus = new Runnable() {
         public void run() {
             /* do something with enclosingClassField */
         }
    }
    /* do something with doUpdateStatus, like SwingUtilities.invokeLater() */
}

I'm really confused. enclosingClassField is not final, it can change every time many times, whereas the poor message argument of updateStatus can only change within its method body, and is instead blamed by the compiler ;)

Even the compiler error is misleading to me. Cannot refer to a non-final variable message inside an inner class defined in a different method : Different from what? Isn't message defined in the same method as the inner class? Isn't enclosingClassField instead defined outside the method? Uhm...

Can someone point me to the correct interpretation of this matter? Thanks.

The difference is between local (method) variables vs class member variables. A member variable exists during the lifetime of the enclosing object, so it can be referenced by the inner class instance. A local variable, however, exists only during the method invocation, and is handled differently by the compiler, in that an implicit copy of it is generated as the member of the inner class. Without declaring the local variable final, one could change it, leading to subtle errors due to the inner class still referring to the original value of that variable.

Update: The Java Specialists' Newsletter #25 discusses this in more detail.

Even the compiler error is misleading to me. Cannot refer to a non-final variable message inside an inner class defined in a different method : Different from what?

From the inner class' run method I believe.

The reason is that Java doesn't support closures . There are no JVM commands to access local variable from outside the method, whereas fields of class can be easily accessed from any place.

So, when you use final local variable in an inner class, compiler actually passes a value of that variable into constructor of the inner class. Obviously, it won't work for non- final variables, since they value can change after construction of the inner class.

Fields of containing class don't have this problem, because compiler implicitly passes a reference to the containing class into the constructor of the inner class, thus you can access its fields in a normal way, as you access fields of any other class.

three types of things: instance variables, local variables,and objects:

■ Instance variables and objects live on the heap.
■ Local variables live on the stack.

Inner class object cannot use the local variables of the method in which the local inner class is defined.

because use local variables of the method is the local variables of the method are kept on the stack and lost as soon as the method ends.

But even after the method ends, the local inner class object may still be alive on the heap. Method local inner class can still use the local variables that are marked final.

final variable JVM takes these as a constant as they will not change after initiated . And when a inner class try to access them compiler create a copy of that variable into the heap and create a synthetic field inside the inner class so even when the method execution is over it is accessible because the inner class has it own copy .

synthetic field are filed which actually doesn't exist in the source code but compiler create those fields in some inner classes to make those field accessible.

The value you use must be final, but the non-final fields of a final reference can be changed. Note: this is implicitly a final reference. You cannot change it.

private String enclosingClassField;
private void updateStatus() {
    final MutableClass ms = new MutableClass(1, 2);
    Runnable doUpdateStatus = new Runnable() {
         public void run() {
             // you can use `EnclosingClass.this` because its is always final
             EnclosingClass.this.enclosingClassField = "";
             // shorthand for the previous line.
             enclosingClassField = "";
             // you cannot change `ms`, but you can change its mutable fields.
             ms.x = 3;
         }
    }
    /* do something with doUpdateStatus, like SwingUtilities.invokeLater() */
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM