我一直在尝试理解Java中嵌套类的机制。
考虑以下代码:
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);
}
我知道内部类是编译器的现象,虚拟机并不知道它们。内部类会被翻译成常规的类文件,并用 $ 符号将外部类和内部类名称分隔开。
为了更详细地了解这个机制,我尝试使用 javap -p 命令反汇编在 Java 版本 1.8 中编译的类文件。
我得到了以下结果: 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 {};
}
内部类:
class staticNestedClasses.OuterClass$InnerClass {
private java.lang.String privateInnerField;
final staticNestedClasses.OuterClass this$0;
staticNestedClasses.OuterClass$InnerClass(staticNestedClasses.OuterClass);
public void accessMembers();
}
在这里,我们可以看到编译器通过构造函数将外部类的引用传递给内部类,以便它可以访问外部类的字段和方法:
staticNestedClasses.OuterClass$InnerClass(staticNestedClasses.OuterClass);
这个外部类的引用被存储在 final staticNestedClasses.OuterClass this$0
中。
但是,OuterClass$InnerClass 类不能通过外部类引用直接访问私有成员,因此当编译器检测到内部类对私有成员的访问时,它会在外部类中生成访问器方法(或getter方法)。
在外部类的反汇编文件中,我们可以看到编译器生成了这些访问器方法。
static java.lang.String access$000(staticNestedClasses.OuterClass);
static java.lang.String access$100();
当我在Java 17.0中编译相同的代码并反汇编类文件时,我得到了以下结果。
外部类:
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 {};
}
这里编译器没有生成任何访问器方法,但代码运行良好。
那么内部类如何访问外部类的私有成员?