内部类 (inner class) 为何可以访问宿主类的成员 (第三部分)
背景
[Java] 内部类 (inner class) 为何可以访问宿主类的成员 (第二部分) 中提到,宿主类的 class
文件中有 NestMembers
属性,内部类的 class
文件中有 NestHost
属性,虚拟机通过查看这两个属性,就可以知道宿主类和内部类的对应关系,从而允许它们访问对方的成员。
但 NestMembers
属性和 NestHost
属性都是 Java 11 才开始出现的属性(Java Virtual Machine Specification 中的 4.7. Attributes 有如下表格),那么在更早的版本(例如 Java 10)中,宿主类和内部类之间这种特殊关系要如何表示呢?
结论
在 Java 11 之前,javac
编译器会在宿主类/内部类中添加合成方法,使得宿主类和内部类可以间接地访问对方的 private
成员。
代码
我们用以下 java
代码来进行探索。请将代码保存为 MyOuter5.java
。
public class MyOuter5 {
private int a;
// a member class
class MemberClass {
private int b = a;
}
void f() {
MemberClass mc = new MemberClass();
int temp = mc.b;
}
}
以下命令可以编译 MyOuter5.java
。(为了查看 Java 11 之前的情况,这里用 --release 10
来指定对应的版本为 Java 10,读者朋友也可以使用早于 Java 11 的其他版本)
javac --release 10 -g -parameters MyOuter5.java
编译后,会得到如下两个 class
文件
MyOuter5.class
MyOuter5$MemberClass.class
用如下命令可以查看 MyOuter5.class
的内容。
javap -v -p 'MyOuter5'
不难验证,里面确实没有 NestMembers
属性了。
完整的结果有点长,我把与字段/方法相关部分粘贴如下(常量池等部分已略去)。
{
private int a;
descriptor: I
flags: (0x0002) ACC_PRIVATE
public MyOuter5();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #7 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LMyOuter5;
void f();
descriptor: ()V
flags: (0x0000)
Code:
stack=3, locals=3, args_size=1
0: new #13 // class MyOuter5$MemberClass
3: dup
4: aload_0
5: invokespecial #15 // Method MyOuter5$MemberClass."<init>":(LMyOuter5;)V
8: astore_1
9: aload_1
10: invokestatic #18 // Method MyOuter5$MemberClass.access$100:(LMyOuter5$MemberClass;)I
13: istore_2
14: return
LineNumberTable:
line 10: 0
line 11: 9
line 12: 14
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 this LMyOuter5;
9 6 1 mc LMyOuter5$MemberClass;
14 1 2 temp I
static int access$000(MyOuter5);
descriptor: (LMyOuter5;)I
flags: (0x1008) ACC_STATIC, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field a:I
4: ireturn
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 x0 LMyOuter5;
}
从上述结果可以反推出对应的 java
代码应该是这样的⬇️
// 以下代码是我手动转化出来的,不保证绝对准确,仅供参考。
public class MyOuter5 {
private int a;
public MyOuter5() {
super();
}
void f() {
MemberClass mc = new MyOuter5$MemberClass(this);
int temp = MyOuter5$MemberClass.access$100(mc); // ⬅️ 这里调用内部类的静态方法(该静态方法不是 private 的,所以宿主类可以正常调用),从而间接访问内部类的 private 字段
}
// ⬇️ 这是编译器合成的静态方法,用于返回宿主类中 a 字段的值。内部类对象通过调用这个静态方法就可以访问宿主类的 a 字段了。
static int access$000(MyOuter5 x0) {
return x0.a;
}
}
让我们用如下命令查看 MyOuter5$MemberClass.class
的内容。
javap -v -p 'MyOuter5$MemberClass'
不难验证,里面确实没有 NestHost
属性了。
以下是与字段/方法相关部分(常量池等部分已略去)。
{
private int b;
descriptor: I
flags: (0x0002) ACC_PRIVATE
final MyOuter5 this$0;
descriptor: LMyOuter5;
flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
MyOuter5$MemberClass(MyOuter5);
descriptor: (LMyOuter5;)V
flags: (0x0000)
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #7 // Field this$0:LMyOuter5;
5: aload_0
6: invokespecial #11 // Method java/lang/Object."<init>":()V
9: aload_0
10: aload_0
11: getfield #7 // Field this$0:LMyOuter5;
14: invokestatic #17 // Method MyOuter5.access$000:(LMyOuter5;)I
17: putfield #1 // Field b:I
20: return
LineNumberTable:
line 5: 0
line 6: 9
line 5: 20
LocalVariableTable:
Start Length Slot Name Signature
0 21 0 this LMyOuter5$MemberClass;
0 21 1 this$0 LMyOuter5;
MethodParameters:
Name Flags
this$0 final mandated
static int access$100(MyOuter5$MemberClass);
descriptor: (LMyOuter5$MemberClass;)I
flags: (0x1008) ACC_STATIC, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field b:I
4: ireturn
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 x0 LMyOuter5$MemberClass;
}
从上述结果可以反推出对应的 java
代码应该是这样的⬇️
// 以下代码是我手动转化出来的,不保证绝对准确,仅供参考。
class MyOuter5$MemberClass {
private int b;
// ⬇️ this$0 是一个合成字段
final MyOuter5 this$0;
// 在内部类的构造函数中会为合成字段 this$0 赋值
MyOuter5$MemberClass(MyOuter5 this$0) {
this.this$0 = this$0;
super();
this.b = MyOuter5.access$000(this.this$0); // ⬅️ 这里通过调用宿主类中的静态合成方法,从而间接访问宿主类的 a 字段。
}
// ⬇️ 这是编译器合成的静态方法,用于返回内部类中 b 字段的值。宿主类对象通过调用这个静态方法就可以访问内部类的 b 字段。
static int access$100(MyOuter5$MemberClass x0) {
return x0.b;
}
}