关于内部类

2019-04-30  本文已影响0人  Wi1ls努力努力再努力

一直说每个方法都会持有外部的引用,每个内部类都会持有外部类的引用。
对于方法具有外部类的引用,在看 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 access000(OuterClass); public synchronized static int 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变量,而引用外部类正是通过编译器在 OuterClass 中自动添加的 static 方法进行获得 private 的实例。而对于 public 的 f4,在 InnerClass直接通过 this.this0.f4 获得。


再做比如删除内部类的实验等几个实验,可以获得以下结论。

上一篇 下一篇

猜你喜欢

热点阅读