美文网首页
内部类可以访问外部类private原因

内部类可以访问外部类private原因

作者: 卡路fly | 来源:发表于2020-05-30 01:24 被阅读0次

    https://www.jianshu.com/p/3132b0641883

    一般的内部类,不仅在形式上和外部类有关系(写在外部类的里面), 在逻辑上也和外部类有联系。 这种逻辑上的关系可以总结为以下两点:

    1 内部类对象的创建依赖于外部类对象;

    2 内部类对象持有指向外部类对象的引用。

    上边的第二条可以解释为什么在内部类中可以访问外部类的成员。就是因为内部类对象持有外部类对象的引用。

    内部类对象持有外部类对象的引用原因

    反编译字节码

    public class Outer {
        int outerField = 0;
         
        class Inner{
            void InnerMethod(){
                int i = outerField;
            }
        }
    }
    

    虽然这两个类写在同一个文件中, 但是编译完成后, 还是生成各自的class文件:
    Outer$Inner.class和Outer.class

    编译内部类的class文件Outer$Inner.class 。 在命令行中, 切换到工程的bin目录, 输入以下命令反编译这个类文件:

    javap -classpath . -v Outer$Inner
    

    -classpath . 说明在当前目录下寻找要反编译的class文件-v 加上这个参数输出的信息比较全面。包括常量池和方法内的局部变量表, 行号, 访问标志等等。
    注意, 如果有包名的话, 要写class文件的全限定名

    {
      final Outer this$0;
        flags: ACC_FINAL, ACC_SYNTHETIC
     
     
      Outer$Inner(Outer);
        flags:
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: aload_1
             2: putfield      #10                 // Field this$0:LOuter;
             5: aload_0
             6: invokespecial #12                 // Method java/lang/Object."<init>":()V
             9: return
          LineNumberTable:
            line 5: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                   0      10     0  this   LOuter$Inner;
     
      void InnerMethod();
        flags:
        Code:
          stack=1, locals=2, args_size=1
             0: aload_0
             1: getfield      #10                 // Field this$0:LOuter;
             4: getfield      #20                 // Field Outer.outerField:I
             7: istore_1
             8: return
          LineNumberTable:
            line 7: 0
            line 8: 8
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                   0       9     0  this   LOuter$Inner;
                   8       1     1     i   I
    }</init>
    

    首先我们会看到, 第一行的信息如下:final Outer this0; 这句话的意思是, 在内部类OuterInner中, 存在一个名字为this$0 , 类型为Outer的成员变量, 并且这个变量是final的。 其实这个就是所谓的“在内部类对象中存在的指向外部类对象的引用”。但是我们在定义这个内部类的时候, 并没有声明它, 所以这个成员变量是编译器加上的。
    虽然编译器在创建内部类时为它加上了一个指向外部类的引用, 但是这个引用是怎样赋值的呢?毕竟必须先给他赋值, 它才能指向外部类对象。 下面我们把注意力转移到构造函数上。 下面这段输出是关于构造函数的信息。

    Outer$Inner(Outer);
      flags:
      Code:
        stack=2, locals=2, args_size=2
           0: aload_0
           1: aload_1
           2: putfield      #10                 // Field this$0:LOuter;
           5: aload_0
           6: invokespecial #12                 // Method java/lang/Object."<init>":()V
           9: return
        LineNumberTable:
          line 5: 0
        LocalVariableTable:
          Start  Length  Slot  Name   Signature
                 0      10     0  this   LOuter$Inner;</init>
    

    我们知道, 如果在一个类中, 不声明构造方法的话, 编译器会默认添加一个无参数的构造方法。 但是这句话在这里就行不通了, 因为我们明明看到, 这个构造函数有一个构造方法, 并且类型为Outer。

    所以说,编译器会为内部类的构造方法添加一个参数, 参数的类型就是外部类的类型。

    下面我们看看在构造参数中如何使用这个默认添加的参数。 我们来分析一下构造方法的字节码。 下面是每行字节码的意义:

    aload_0 : 将局部变量表中的第一个引用变量加载到操作数栈

    这里有几点需要说明

    1. 局部变量表中的变量在方法执行前就已经初始化完成
    2. 局部变量表中的变量包括方法的参数
    3. 成员方法的局部变量表中的第一个变量永远是this
    4. 操作数栈就是执行当前代码的栈。
      所以这句话的意思是: 将this引用从局部变量表加载到操作数栈。

    aload_1:
    将局部变量表中的第二个引用变量加载到操作数栈。 这里加载的变量就是构造方法中的Outer类型的参数。
    putfield #10 // Field this0:LOuter; 使用操作数栈顶端的引用变量为指定的成员变量赋值。 这里的意思是将外面传入的Outer类型的参数赋给成员变量this0 。 这一句putfield字节码就揭示了, 指向外部类对象的这个引用变量是如何赋值的。

    总结

    本文通过反编译内部类的字节码, 说明了内部类是如何访问外部类对象的成员的,除此之外, 我们也对编译器的行为有了一些了解,编译器在编译时会自动加上一些逻辑, 这正是我们感觉困惑的原因。

    关于内部类如何访问外部类的成员, 分析之后其实也很简单, 主要是通过以下几步做到的:

    1 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象的引用;

    2 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为1中添加的成员变量赋值;

    3 在调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用。

    相关文章

      网友评论

          本文标题:内部类可以访问外部类private原因

          本文链接:https://www.haomeiwen.com/subject/rawqzhtx.html