I have been trying to understand the mechanism of nested classes in java.
Consider the following code:
public class OuterClass {
private String privateOuterField = "Private Outer Field";
public static String publicStaticOuterField = "Public static outer field";
private static String privateStaticOuterField = "private static outer field";
class InnerClass{
private String privateInnerField = "Private Inner Field";
//non-final static data members not allowed in java 1.8 but allowed in java 17.0
//private static String innerClassStaticField = "Private Inner Class Static Field";
public void accessMembers() {
System.out.println(privateOuterField);
System.out.println(publicStaticOuterField);
}
}
static class StaticInnerClass{
private String privateStaticInnerField = "Private Inner Field of static class";
public void accessMembers(OuterClass outer) {
//System.out.println(privateOuterField); //error
System.out.println(outer.privateOuterField);
System.out.println(publicStaticOuterField);
System.out.println(privateStaticOuterField);
}
}
public static void main(String[] args) {
OuterClass outerObj = new OuterClass();
OuterClass.InnerClass innerObj = outerObj.new InnerClass();
StaticInnerClass staticInnerObj = new StaticInnerClass();
innerObj.accessMembers();
staticInnerObj.accessMembers(outerObj);
}
}
I know that inner classes are a phenomenon of the compiler, virtual machine are not aware of them. Inner classes are translated into regular class files with $ delimiting outer and inner class name.
To understand this mechanism in more details, I tried to disassemble the class file compiled in java version 1.8 using javap -p command.
I got the following results: OuterClass:
public class staticNestedClasses.OuterClass {
private java.lang.String privateOuterField;
public static java.lang.String publicStaticOuterField;
private static java.lang.String privateStaticOuterField;
public staticNestedClasses.OuterClass();
public static void main(java.lang.String[]);
static java.lang.String access$000(staticNestedClasses.OuterClass);
static java.lang.String access$100();
static {};
}
InnerClass:
class staticNestedClasses.OuterClass$InnerClass {
private java.lang.String privateInnerField;
final staticNestedClasses.OuterClass this$0;
staticNestedClasses.OuterClass$InnerClass(staticNestedClasses.OuterClass);
public void accessMembers();
}
Here we can see that the compiler passes the reference of outer class to inner class through constructor so that it can assess outer class's fields and methods:
staticNestedClasses.OuterClass$InnerClass(staticNestedClasses.OuterClass);
this outer class reference is stored in final staticNestedClasses.OuterClass this$0
But OuterClass$InnerClass class cannot directly access private members through outer class reference, so whenever the compiler detects access to private members from inner class, it generates accessor method (or getter methods) in outer class.
In the disassembled file of outer class we can see that the compiler generated these accessor methods.
static java.lang.String access$000(staticNestedClasses.OuterClass);
static java.lang.String access$100();
But when I compiled the same code in java 17.0 and disassembled the class file I got the following result.
Outer class:
public class staticNestedClasses.OuterClass {
private java.lang.String privateOuterField;
public static java.lang.String publicStaticOuterField;
private static java.lang.String privateStaticOuterField;
public staticNestedClasses.OuterClass();
public static void main(java.lang.String[]);
static {};
}
OuterClass$InnerClass:
class staticNestedClasses.OuterClass$InnerClass {
private java.lang.String privateInnerField;
private static java.lang.String innerClassStaticField;
final staticNestedClasses.OuterClass this$0;
staticNestedClasses.OuterClass$InnerClass(staticNestedClasses.OuterClass);
public void accessMembers();
static {};
}
Here compiler did not generate any accessor methods but the code worked fine.
So how did the inner class access private members of outer class?