Why is an anonymous inner class containing nothing generated from this code?
I'm using polygenelubricants's smaller snippet.
Remember there's no concept of nested classes in the bytecode; the bytecode is, however, aware of access modifiers. The problem the compiler is trying to circumvent here is that
the method instantiate()
needs to create a new instance of PrivateInnerClass
. However, OuterClass
does not have access to PrivateInnerClass
's constructor (OuterClass$PrivateInnerClass
will be generated as a package-protected class without a public constructor).
So what can the compiler do? The obvious solution is to change PrivateInnerClass
to have a package-protected constructor. The problem here is that this will allow any other code which interfaces with the class to create a new instance of PrivateInnerClass
, even though it's explicitly declared as private!
To try and prevent that, the javac compiler is doing a little trick: instead of making PrivateInnerClass
's regular constructor visible from other classes, it leaves it as hidden (actually it does not define it at all, but that's the same thing from outside). Instead, it creates a new constructor which receives an additional parameter of the special type OuterClass$1
.
Now, if you look at instantiate()
, it calls that new constructor. It actually sends null
as the 2nd parameter (of the type OuterClass$1
) - that parameter is only used for specifying that this constructor is the one that should be called.
So, why create a new type for the 2nd parameter? Why not use, say, Object
? It's only used to differentiate it from the regular constructor and null
is passed anyway! And the answer is that as OuterClass$1
is private to OuterClass, a legal compiler will never allow the user to invoke the special OuterClass$PrivateInnerClass
constructor, as one of the required parameter types, OuterClass$1
, is hidden.
I'm guessing JDT's compiler uses another technique to solve the same problem.
I don't have the answer, but I'm able to confirm that, and reduce the snippet to the following:
public class OuterClass {
private class PrivateInnerClass {
}
public void instantiate() {
new PrivateInnerClass();
}
}
This creates OuterClass$1.class
Compiled from "OuterClass.java"
class OuterClass$1 extends java.lang.Object{
}
And here's javap -c
for OuterClass.class
:
Compiled from "OuterClass.java"
public class OuterClass extends java.lang.Object{
public OuterClass();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public void instantiate();
Code:
0: new #2; //class OuterClass$PrivateInnerClass
3: dup
4: aload_0
5: aconst_null
6: invokespecial #3; //Method OuterClass$PrivateInnerClass."<init>":
//(LOuterClass;LOuterClass$1;)V
9: pop
10: return
}
And for OuterClass$PrivateInnerClass
:
Compiled from "OuterClass.java"
class OuterClass$PrivateInnerClass extends java.lang.Object{
final OuterClass this$0;
OuterClass$PrivateInnerClass(OuterClass, OuterClass$1);
Code:
0: aload_0
1: aload_1
2: invokespecial #1; //Method "<init>":(LOuterClass;)V
5: return
}
As you can see, the synthesized constructor takes an OuterClass$1
argument.
So javac
creates the default constructor to take an extra argument, of type $1
, and the value of that default argument is 5: aconst_null
.
I've found that $1
doesn't get created if either of the following is true:
- You make
public class PrivateInnerClass
- You declare a nullary constructor for
PrivateInnerClass
- Or you don't call the
new
on it - Probably other things (e.g.
static
nested, etc).
Possibly related
- Bug ID:4295934: Compiling a private inner class creates an anonymous class file in the wrong dir
Create the following source in a directory called test:
package test; public class testClass { private class Inner { } public testClass() { Inner in = new Inner(); } }
Compile the file from the parent directory
javac test/testClass.java
Notice that the file
testClass$1.class
is created in the current directory. Not sure why this file is even created since there is also atest/testClass$Inner.class
created as well.EVALUATION
The
testClass$1.class
file is for a dummy class needed by an "access constructor" for the private constructor of the private inner classtestClass$Inner
. Dissassembly shows that the fully-qualified name of this class is correctly noted, so it is unclear why the class file ends up in the wrong directory.