36

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.

gd1
  • 11,300
  • 7
  • 49
  • 88

4 Answers4

34

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.

Péter Török
  • 114,404
  • 31
  • 268
  • 329
  • 7
    So Java could copy the value of the variable w/o bothering with this compiler error, but it instead forces you to declare the variable as final to tell you "hey, remember it gets copied into the inner class, so if you could change it afterwards, then you get a severe inconsistency. So you can't change it, and we're clear with that." – gd1 Apr 27 '11 at 09:27
  • 3
    @Giacomo, exactly. The designers attempted to minimize the number of ways developers can shoot themselves in the foot :-) This in turn may result in unintuitive (at first sight) behaviour by the compiler, but IMHO it is still much better than unintuitive behaviour from your running program. – Péter Török Apr 27 '11 at 09:30
17

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.

axtavt
  • 239,438
  • 41
  • 511
  • 482
11

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.

rajeev pani..
  • 5,387
  • 5
  • 27
  • 35
4

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() */
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Yeah I know, it's quite different than C++ const. Thanks. – gd1 Apr 27 '11 at 09:33
  • 2
    `final` is a shallow constant, whereas C++ `const` is the whole object and anything its references. In Java, even `enum`s can be mutable ;) – Peter Lawrey Apr 27 '11 at 09:35