I am very curious about this question. The Java Language Specification has told me part of the reason:
- In a class instance creation expression for a non-private inner member class, §15.9.2 specifies the immediately enclosing instance of
the member class. The member class may have been emitted by a compiler
which is different than the compiler of the class instance creation
expression. Therefore, there must be a standard way for the compiler
of the creation expression to pass a reference (representing the
immediately enclosing instance) to the member class’s constructor.
Consequently, the Java programming language deems in this section that
a non-private inner member class’s constructor implicitly declares an
initial parameter for the immediately enclosing instance. §15.9.3
specifies that the instance is passed to the constructor. …
But for me, this is still not detailed enough.
For example, why is it that because of “The member class may have been emitted by a compiler which is different than the compiler of the class instance creation
expression.”, so “there must be a standard way for the compiler
of the creation expression to pass a reference (representing the
immediately enclosing instance) to the member class’s constructor.”?
I can’t figure out what causal relationship exists between these two sentences.
I have read some related texts, such as this one and this one.But I still feel puzzled.
Could someone please explain this passage to me in detail?
It would be even better if an example could be combined.
Thank you for your reading.
0
An inner (non-static) class has the seemingly magical ability to reference the creating instance of the outer class, but it’s actually just syntactic sugar. The compiler produces a normal class with an extra reference to the outer class passed into the constructor.
So what looks like this:
class Outer {
class Inner {}
}
Effectively compiles to something like this:
class Outer {
final Outer outer;
static class Inner {
Inner(Outer outer) {
this.outer = outer;
}
}
}
Now, consider the caller:
Outer o = new Outer();
Inner i = o.new Inner();
This has to be translated by the compiler to:
Outer o = new Outer();
Inner i = new Inner(o);
But what if the inner class already has explicit constructor args? Do we call new Inner(o, arg0, arg1, ..., argN)
or new Inner(arg0, arg1, ..., argN, o)
? If we could guarantee the class and its users are built by the same compiler, the answer would be, “Who cares? It’s not exposed to the code anyway.*” But classes need to be binary compatible, which means compiler A might produce class Inner
and compiler B might produce the bytecode that’s using it, and B needs to know how to pass in Outer
when instantiating Inner
. Hence the JLS specification that it’s to be inserted as the first constructor argument.
*except via reflection, but let’s not go there
12
Suppose there is no such requirement on how the enclosing instance is passed, and suppose that there are compiler implementations A and B.
Let’s use compiler A to compile
public class Outer {
public void foo() {
}
public class Inner {
public Inner(Outer anotherOuter) {
// Inner should be able to access the members of the enclosing instance here
// Note that this is different from anotherOuter.foo()
Outer.this.foo();
}
}
}
$ javacA Outer.java
This produces two files – Outer.class and Outer$Inner.class. Compiler A has put the anotherOuter
parameter as the first parameter of the Inner
constructor, and uses the second parameter to take the enclosing instance.
Then we use compiler B to compile
public class JavaClass
{
public static void main (String [] args)
{
Outer o1 = new Outer();
Outer o2 = new Outer();
Outer.Inner i = o1.new Inner(o2);
}
}
# assuming Outer.class and Outer$Inner.class are on the class path
javacB JavaClass.java
But compiler B has a problem, what parameter should it pass to the constructor of Outer.Inner
? From its perspective, Inner
‘s constructor takes two Outer
parameters, but it doesn’t know which one is supposed to take the enclosing instance o1
, and which one is supposed to take o2
!
This is why the spec needs to specify this, essentially providing a way to identify which parameter should the enclosing instance be passed to.
Of course, it doesn’t have to be the first parameter. This is just a design choice. The spec could have said that “the parameter with the name of <insert some name here> should be passed the enclosing instance” or many other variations.
7