Are all final variables captured by anonymous classes?

  • A+
Category:Languages

I thought I knew the answer to this, but I can't find any confirmation after an hour or so of searching.

In this code:

public class Outer {      // other code      private void method1() {         final SomeObject obj1 = new SomeObject(...);         final SomeObject obj2 = new SomeObject(...);         someManager.registerCallback(new SomeCallbackClass() {             @Override             public void onEvent() {                  System.out.println(obj1.getName());             }         });     } } 

Assume that registerCallback saves its parameter somewhere, so that the object of the anonymous subclass will live for a while. Obviously this object has to maintain a reference to obj1 so that onEvent will work if it is called.

But given that the object doesn't use obj2, does it still maintain a reference to obj2, so that obj2 can't be garbage-collected while the object lives? I was under the impression that all visible final (or effectively final) local variables and parameters were captured and thus couldn't be GC'ed as long as the object was alive, but I can't find anything that says one way or the other.

Is it implementation-dependent?

Is there a section in the JLS that answers this? I wasn't able to find the answer there.

 


Only obj1 is captured.

Logically, the anonymous class is implemented as a normal class something like this:

class Anonymous1 extends SomeCallbackClass {     private final Outer _outer;     private final SomeObject obj1;     Anonymous1(Outer _outer, SomeObject obj1) {         this._outer = _outer;         this.obj1 = obj1;     }     @Override     public void onEvent() {          System.out.println(this.obj1.getName());     } }); 

Note that an anonymous class is always an inner class, so it will always maintain a reference to the outer class, even if it doesn't need it. I don't know if later versions of the compiler have optimized that away, but I don't think so. It is a potential cause of memory leaks.

The use of it becomes:

someManager.registerCallback(new Anonymous1(this, obj1)); 

As you can see, the reference value of obj1 is copied (pass-by-value).

There is technically no reason for obj1 to be final, whether declared final or effectively final (Java 8+), except that if it wasn't and you change the value, the copy wouldn't change, causing bugs because you expected the value to change, given that the copying is a hidden action. To prevent programmer confusion, they decided that obj1 must be final, so you can never become confused about that behavior.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: