Inner Classes Specification
How do inner classes work?
Note: This Inner Classes Specification is available for download as part of the JDK1.1 End Of Life (EOL) section of the Sun website. It has been included here because most of the specification is still relevant to the current Java classfile definition. However, the information has not been transferred into the latest Java Virtual Machine Specification or made available elsewhere in Sun's online Java resources.
Inner class code is typically defined relative to some enclosing class instance,
so the inner class instance needs to be able to determine the enclosing instance.
The JavaSoft Java 1.1 compiler arranges this by adding an extra private
instance variable which links the inner class to the enclosing class. This
variable is initialized from an extra argument passed to the inner class
constructor. That argument, in turn, is determined by the expression which
creates the inner class instance; by default it is the object doing the creation.
The Java 1.1 Language Specification specifies that the name of a type which is
a class member, when transformed into Java 1.0 code for the purpose of
generating Java virtual machine bytecodes, consists of the fully qualified name
of the inner class, except that each `.' character following a class name is
replaced by a `$'. In addition, each inner class constructor receives the
enclosing instance in a prepended argument. Here is how the transformed
source code of the FixedStack example might look:
public class FixedStack {
... (the methods omitted here are unchanged)
public java.util.Enumeration elements() {
return new FixedStack$Enumerator(this);
}
}
class FixedStack$Enumerator implements java.util.Enumeration {
private FixedStack this$0; // saved copy of FixedStack.this
FixedStack$Enumerator(FixedStack this$0) {
this.this$0 = this$0;
this.count = this$0.top;
}
int count;
public boolean hasMoreElements() {
return count > 0;
}
public Object nextElement() {
if (count == 0)
throw new NoSuchElementException("FixedStack");
return this$0.array[--count];
}
}
|
Anyone who has already programmed with Java or C++ adapter classes has
written code similar to this, except that the link variables must be manually
defined and explicitly initialized in top-level adapter classes, whereas the Java
1.1 compiler creates them automatically for inner classes.
When the Enumerator needs to refer to the top or array fields of the
enclosing instance, it indirects through a private link called this$0. The
spelling of this name is a mandatory part of the transformation of inner classes
to the Java 1.0 language, so that debuggers and similar tools can recognize
such links easily. (Most programmers are happily unaware of such names.)
(Note: There is a limitation in some implementations of Java 1.1, under which
the initialization of this$0 is delayed until after any superclass constructor is
run. This means that up-level references made by a subclass method may fail
if the method happens to be executed by the superclass constructor.)
References to local variables
A class definition which is local to a block may access local variables. This
complicates the compiler's job. Here is the previous example of a local class:
Enumeration myEnumerate(final Object array[]) {
class E implements Enumeration {
int count = 0;
public boolean hasMoreElements()
{ return count < array.length; }
public Object nextElement() {
{ return array[count++]; }
}
return new E();
}
|
In order to make a local variable visible to a method of the inner class, the
compiler must copy the variable's value into a place where the inner class can
access it. References to the same variable may use different code sequences in
different places, as long as the same value is produced everywhere, so that the
name consistently appears to refer to the same variable in all parts of its scope.
By convention, a local variable like array is copied into a private field
val$array of the inner class. (Because array is final, such copies never
contain inconsistent values.) Each copied value is passed to the inner class
constructor as a separate argument of the same name.
Here is what the resulting transformed code looks like:
Enumeration myEnumerate(final Object array[]) {
return new MyOuterClass$19(array);
}
...
class MyOuterClass$19 implements Enumeration {
private Object val$array[];
int count;
MyOuterClass$19(Object val$array[])
{ this.val$array = val$array; count = 0; }
public boolean hasMoreElements()
{ return count < val$array.length; }
public Object nextElement()
{ return val$array[count++]; }
}
|
A compiler may avoid allocating an inner class field to a variable, if it can
determine that the variable is used only within the inner class constructors.
Notice that a class defined by a block, like E, is not a member of its enclosing
class, and so it cannot be named outside of its block. This is the same scoping
restriction as applies to local variables, which also cannot be named outside of
their blocks. In fact, any class contained in a block (whether directly or inside
an intervening local class) cannot be named outside the block. All such classes
are called inaccessible. For purposes of linking, the compiler must generate a
unique externally visible name for every inaccessible class. The overall form of
these names is a class name, followed by additional numbers or names,
separated by $ characters.
Also, variable names synthesized by the compiler beginning with this$ and
val$ must follow the usage patterns described here.
These names and conventions must be recognized by 1.1-compliant tools, and
are strongly suggested for most compilation purposes. They are discussed
further in the section on binary compatibility.
It must be emphasized that these oddly-named "this$" and "val$" fields
and extra constructor arguments are added by the compiler to the generated
bytecodes, and cannot be directly referenced by Java source code. Likewise,
bytecode-level class names like MyOuterClass$19 cannot be used by source
code (except under pre-1.1 compilers, which know nothing of inner classes).
|