Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do anonymous classes capture "this" even if they don't need to?

Given this code:

class Foo {}

public class Test {
        public Foo makeFoo(String p, String q) {
                return new Foo(){
                        public void doSomething() {
                                System.out.println(p);
                        }
                };
        }
}

When you compile that and run javap -c -p 'Test$1.class', you get this:

Compiled from "Test.java"
class Test$1 extends Foo {
  final java.lang.String val$p;

  final Test this$0;

  Test$1(Test, java.lang.String);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:LTest;
       5: aload_0
       6: aload_2
       7: putfield      #2                  // Field val$p:Ljava/lang/String;
      10: aload_0
      11: invokespecial #3                  // Method Foo."<init>":()V
      14: return

  public void doSomething();
    Code:
       0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #2                  // Field val$p:Ljava/lang/String;
       7: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      10: return
}

When the anonymous class is created, the variable p is captured into val$p (as expected, because it's needed), and the variable q is not (as expected, because it's not needed). However, Test.this is captured into this$0 even though it isn't needed. Is this mandated by the Java specification, or is it just the way it happens to work? Why does it work this way?

like image 251
Joseph Sible-Reinstate Monica Avatar asked Jan 05 '18 02:01

Joseph Sible-Reinstate Monica


2 Answers

Because it is an inner class, and because

An instance i of a direct inner class C of a class or interface O is associated with an instance of O, known as the immediately enclosing instance of i. The immediately enclosing instance of an object, if any, is determined when the object is created (§15.9.2).

JLS 8.1.3.

There is no exception for 'even if they don't need to'.

like image 55
user207421 Avatar answered Sep 20 '22 02:09

user207421


  1. Because it is simpler to do it that way. Fewer code paths in the bytecode compiler, for example.

  2. Because if they treated the cases where this capture is necessary or unnecessary as different cases (i.e. by altering the effective constructor signature) then this would create huge problems for code that needs to create instances using reflection, byte-code engineering, etc.

Now the flipside of this is that it probably doesn't matter. The bytecodes are JIT compiled, and the JIT compiler should be capable of optimizing away unused variables (like this$0). If it is worthwhile to optimize away the passing of the hidden variable, this will be done by the JIT compiler as well.

Note this: You cannot make sound judgments on Java code efficiency by looking at the bytecode sequences. You really need to look at the native code emitted by the JIT compiler.


UPDATE - The stuff I wrote above about the JIT compiler's capability is speculative. However, if it turns out that there is a fundamental reason why the JIT compiler cannot optimize away an unused this$0, then that is most likely also a reason why the bytecode compiler can't do this either. (I am thinking of what happens when you debug the application.)

like image 30
Stephen C Avatar answered Sep 21 '22 02:09

Stephen C