Nested classes are classes defined within classes. We distinguish between 4 different kinds of nested classes.
-
Static inner classes
-
Nested inner classes
-
Method-local inner classes
-
Anonymous Inner classes
Regardless of type, all nested classes share a common feature in that they are compiled into their own separate class files.
The relation between static inner classes and their enclosing outer classes is purely structural. A static inner class has no special access to members of the enclosing class, the outer class is only used as a namespace prefix for the inner class. See the following code as an example, where the static a inner class called "Inner" is nested within a class called "Outer".
public class Outer {
public static class Inner {
// ...
}
}
The above static inner class can be instantiated by the following expression:
Outer.Inner inner = new Outer.Inner();
As stated before, static inner classes are compiled into separate class files. In this case the Inner
class will be renamed Outer$Inner
and compiled into an Outer$Inner.class
file. With the class hierarchy flattened, the actual compiled code instantiating the class will look like this:
Outer$Inner inner = new Outer$Inner();
Caution
|
Defining classes with names similar to Outer$Inner , while not prohibited, should be avoided, because these names may clash with other compiler generated class names. An example error message signaling such a problem would look something like the following: "Error: duplicate class: Outer.Inner".
|
Nested inner classes, are all bound to a given instance of their outer class and have full access to all their members, including ones with private visibility. Like static inner classes, nested inner classes are also compiled into separate class files. See the following code as an example, where a nested inner class called "Inner" is nested within a class called "Outer".
public class Outer {
public int outerValue = 0;
public void outerMethod(int value) {
// ...
}
public class Inner {
public void innerMethod(int value) {
System.out.println(outerValue);
outerValue = value;
outerMethod(value);
}
}
}
As stated previously, nested inner classes are bound to a specific instance of the outer class they are nested within. As can be seen in the following example, the instantiation of nested inner classes is handled through an instance of the outer class.
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
Similar to static inner classes, nested inner classes are also renamed and compiled into separate class files. The compiler will also modify the inner class by adding an extra final
field. This field is most often called this$0
and is intended to hold a reference to an instance the outer class. The constructor signature of the inner class will be extended to accept an extra 0th parameter of the type of the outer class, and the the constructor code is added instructions that assign this parameter to the aforementioned this$0
field. Statements that reference public
members of the outer class will be modified to use reference stored in the this$0
variable. The compiled equivalent of the example inner class will resemble the following:
public class Outer$Inner
final Outer this$0;
public Outer$Inner(Outer this$0) {
this.this$0 = this$0;
}
public void innerMethod(int value) {
System.out.println(this$0.outerValue));
this$0.outerValue = value;
this$0.outerMethod(value);
}
}
Note
|
The compiler will resolve name clashes between existing class fields and generated this$0 identifiers, by appending $ characters to the name of the synthetic field until the clash is finally resolved.
|
Code used for instantiating the class is also be modified to pass a reference of the outer class as an argument of the newly introduced constructor parameter. The code compiled for the sample demonstrating the instantiation of nested inner classes will therefore resemble the following:
Outer outer = new Outer();
Outer$Inner inner = new Outer$Inner(outer);
Before the introduction of the notion of nestmates in Java 11, nested inner classes had no special privileged access to the private
members of their enclosing class. To allow nested inner classes access to these members, the Java compiler essentially had to break the encapsulation of the outer class by introducing static accessor and mutator methods for each private member. This can be demonstrated in the following example:
public class Outer {
private int outerValue = 0;
private void outerMethod(int value) {
// ...
}
public class Inner {
public void innerMethod(int value) {
System.out.println(outerValue);
outerValue = value;
outerMethod(value);
}
}
}
Examining the compiled equivalent of the Outer
class, reveals a number of static
helper methods, generated solely for providing the Inner
class with access to various private class members.
public class Outer {
private int outerValue = 0;
private void outerMethod(int value) {
// ...
}
// Synthetic accessor of outerValue
static int access$000(Outer x0) {
return x0.outerValue;
}
// Synthetic mutator of outerValue
static int access$002(Outer x0, int x1) {
return (x0.outerValue = x1);
}
// Synthetic delegate to outerMethod(int)
static void access$100(Outer x0, int x1) {
x0.outerMethod(x1);
}
}
The code of the Inner
class is also modified to use the previously generated helper methods when accessing the otherwise restricted members of the outer class.
public class Outer$Inner
final Outer this$0;
public Outer$Inner(Outer this$0) {
this.this$0 = this$0;
}
public void innerMethod(int value) {
System.out.println(Outer.access$000(this.this$0));
Outer.access$002(this.this$0, value);
Outer.access$100(this.this$0, value);
}
}
Caution
|
Defining methods with names that start with the access$ prefix should be avoided, because these names may clash with compiler generated helper methods, resulting in compile-time time errors. An example error message signaling such a problem would look something like the following: "Error: The symbol access$000(Outer) conflicts with a compiler-synthesized symbol in Outer".
|
To address the shortcomings of the implementation of nested inner classes discussed in the previous section, Java 11 introduced the notion of nestmates. Nestmates introduce a new access control mechanism that allows a nested inner classes (nest members) to access private
members of their enclosing outer class (the nest host) without the use of synthetic accessor methods, if both classes belong to the same nest. Nests are internally implemented as class attributes on both the enclosing and the nested classes. The enclosing class will have a NestMembers
attribute, listing the names of all the classes that are "enclosed" within the class. Conversely, the compiled nested inner class will have a NestHost
attribute set to the name of the enclosing class.
Nest-based access control functions as follows: A class named A will have access to the private
members of another class named B if class A has a NestHost
class attribute set to the class name of B AND class B has a NestMembers
class attribute containing the class name of A
.
Recompiling the example class presented under the synthetic methods section with Java 11 will output classes that accesses private members without the use of synthetic accessor methods. Access to private members of the enclosing class will be granted by the JVM based on the nestmate relationship encoded into classes' NestMembers
and NestHost
attributes.
public class Outer {
// NestMembers attribute: Outer$Inner
private int outerValue = 0;
private void outerMethod(int value) {
// ...
}
}
public class Outer$Inner {
// NestHost attribute: class Outer
final Outer this$0;
public Outer$Inner(Outer this$0) {
this.this$0 = this$0;
}
public void innerMethod(int value) {
System.out.println(this$0.outerValue));
this$0.outerValue = value;
this$0.outerMethod(value);
}
}
Method local classes are classes that are declared within the bodies of other methods. Method local classes defined within instance methods are essentially nested inner classes, while those defined within static methods are static inner classes. A method local class can only be referenced inside the method it was declared in.
In addition to the capabilities of their respective implementations, method local classes can also reference final
local variables from their enclosing method’s lexical context. Since Java 8, this access also extends to effectively final local variables. These variables, while not explicitly declared final
, are set only once in the context of a method.
Captured local variables are internally stored in method local classes as final
fields named val$
, and followed by the name of the variable. The actual values of the captured variables are injected into the local class via synthetically added constructor parameters. Synthetic parameters will be added before other defined constructor parameters.
The following code snippet is an example of a method local class capturing a local variable:
public class Outer {
public void printHello() {
String greeting = "Hello!";
class Greeter {
public void greet() {
System.out.println(greeting);
}
}
Greeter greeter = new Greeter();
greeter.greet(); // Prints "Hello!"
}
}
The above code is compiled into the semantic equivalent of the following nested inner class combination.
public class Outer {
class Greeter {
final String val$greeting;
public Greeter(String $val1) {
this.val$greeting = $val1;
}
public void greet() {
System.out.println(this.val$greeting);
}
}
public void printHello() {
String greeting = "Hello!";
Greeter greeter = new Greeter(greeting);
greeter.greet(); // Prints "Hello!"
}
}
Note
|
Parameters used to inject captured variables into method local classes are actually unnamed. The constructor parameter named $val1 in the above code is only used for convenience.
|
Caution
|
Defining fields in method local classes that start with the val$ prefix should be avoided, because these names may clash with compiler generated fields created to store the values of captured local variables. An example error message signaling such a problem would look something like the following: "Error while generating class Greeter (the symbol val$greeting conflicts with a compiler-synthesized symbol in Greeter)"
|