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?
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'.
Because it is simpler to do it that way. Fewer code paths in the bytecode compiler, for example.
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.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With