一直说每个方法都会持有外部的引用,每个内部类都会持有外部类的引用。
对于方法具有外部类的引用,在看 jvm 的时候发现编译器在每个非静态方法都会插入 solot 为 0 的一个入参,改入参即为调用方法的对象。
对于内部类持有外部类的引用,也是今天通过反编译时候发现了其实现。
定义的 OuterClass和 InnerClass
public class OuterClass {
private String f1 = "en";
private int f2 = 5;
private Object f3 = new Object();
public float f4;
public class InnerClass {
public void printOuterClassPrivateFields() {
String f11 = f1;
int f22 = f2;
Object f33 = f3;
float f44 = f4;
}
}
public synchronized static void method_outer() {
}
}
当我们进行 javap -v 时:
...
static int access$100(OuterClass);
descriptor: (LOuterClass;)I
flags: ACC_STATIC, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field f2:I
4: ireturn
LineNumberTable:
line 7: 0
static java.lang.Object access$200(OuterClass);
descriptor: (LOuterClass;)Ljava/lang/Object;
flags: ACC_STATIC, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field f3:Ljava/lang/Object;
4: areturn
LineNumberTable:
line 7: 0
}
SourceFile: "OuterClass.java"
InnerClasses:
public #9= #8 of #7; //InnerClass=class OuterClass$InnerClass of class OuterClass
可以看到莫名增加了几个在源文件没有定义的方法:
public synchronized static String access100(OuterClass);
public synchronized static Object access$200(OuterClass);
而从方法的字节码可以看出来,这三个方法正是返回 f1,f2,f3 的三个 static 方法,这三个方式是编译器自动生成的。而声明为 public的f4 却没有生成对应的方法。在看 InnerClass的printOuterClassPrivateFields()方法:
final OuterClass this$0;
descriptor: LOuterClass;
flags: ACC_FINAL, ACC_SYNTHETIC
public OuterClass$InnerClass(OuterClass);
descriptor: (LOuterClass;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LOuterClass;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 13: 0
public void printOuterClassPrivateFields();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=5, args_size=1
0: aload_0
1: getfield #1 // Field this$0:LOuterClass;
4: invokestatic #3 // Method OuterClass.access$000:(LOuterClass;)Ljava/lang/String;
7: astore_1
8: aload_0
9: getfield #1 // Field this$0:LOuterClass;
12: invokestatic #4 // Method OuterClass.access$100:(LOuterClass;)I
15: istore_2
16: aload_0
17: getfield #1 // Field this$0:LOuterClass;
20: invokestatic #5 // Method OuterClass.access$200:(LOuterClass;)Ljava/lang/Object;
23: astore_3
24: aload_0
25: getfield #1 // Field this$0:LOuterClass;
28: getfield #6 // Field OuterClass.f4:F
31: fstore 4
33: return
LineNumberTable:
line 15: 0
line 16: 8
line 17: 16
line 18: 24
line 19: 33
可以看到,在内部类的构造方法中,编译器帮我们帮外部类 OuterClass的引用自动放到了 InnerClass的构造方法的第一个参数(所有构造方法都会自动添加,就像所有的非静态方法的第一个 slot 都是 this 引用),通过 IDE 直接查看 OuterClass$InnerClass.class 可以看到
public class OuterClass$InnerClass {
public OuterClass$InnerClass(OuterClass var1) {
this.this$0 = var1;
}
public void printOuterClassPrivateFields() {
String var1 = OuterClass.access$000(this.this$0);
int var2 = OuterClass.access$100(this.this$0);
Object var3 = OuterClass.access$200(this.this$0);
float var4 = this.this$0.f4;
}
}
构造函数中将外部类定义为内部类的一个 this0.f4 获得。
再做比如删除内部类的实验等几个实验,可以获得以下结论。
- 编译器在内部类的构造函数中,会自动添加外部类的引用作为构造函数的第一个参数,在内部中变量名为 this$0。
- 当一个类不具有内部类,或者其内部类没有引用外部类的 private 字段时,或者没有引用外部类的 private 方法时,则不会生成对应的public synchronized xxx access$XXX(OuterClass)方法。
- 如果一个类的内部类引用了外部类的 private 字段,则会在外部类生成对应的 static access
XXX(OuterClass)方法,内部进行 invoke 调用对应的 private 方法。
- 同理,外部类能引用内部类的 private 字段或者方法,也是通过编译器在内部类生成对应的 access
InnerClass)的方法去实现的。并且当且仅当是外部类才可以引用。
- 以上的一切都是编译器自动优化生成,并且只有引用了才会生成,不引用不会生成。可以视为惰性生成。
网友评论