zoukankan      html  css  js  c++  java
  • 学以致用,通过字节码理解:Java的内部类与外部类之私有域访问

    目录:

    • 内部类的定义及用处
    • 打开字节码理解内部类

    一、内部类的定义及用处

    内部类(inner class)是定义在另一个类中的类。使用内部类,我们可以:

    • 访问该类定义所在的作用域中的数据,包括私有的数据
    • 可以对同一个包中的其他类隐藏起来
    • 当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷

    本文旨在讲解内部类与外部类可以相互访问对方的私有域的原理,内部类的用法等大家可以自行查阅(官网介绍简单明了:Nested Class);

     

    二、打开字节码理解内部类

    我们知道,内部类其实是Java语言的一种语法糖。经过编译会生成一个"外部类名$内部类名.class"的class文件。如下:


    非常简单的一个类OuterCls,包含了一个InnerCls内部类。通过javac编译,我们可以看到列表中多了一个文件:OuterCls$InnerCls.class。

    接着,我们通过javap -verbose查看生成的OuterCls.class:

     1 $ javap -verbose OuterCls
     2 Warning: File ./OuterCls.class does not contain class OuterCls
     3 Classfile /Users/ntchan/code/demo/concepts/src/com/ntchan/nestedcls/OuterCls.class
     4   Last modified Aug 14, 2018; size 434 bytes
     5   MD5 checksum b9a1f41c67c8ae3be427c578ea205d20
     6   Compiled from "OuterCls.java"
     7 public class com.ntchan.nestedcls.OuterCls
     8   minor version: 0
     9   major version: 53
    10   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
    11   this_class: #3                          // com/ntchan/nestedcls/OuterCls
    12   super_class: #4                         // java/lang/Object
    13   interfaces: 0, fields: 1, methods: 2, attributes: 2
    14 Constant pool:
    15    #1 = Fieldref           #3.#18         // com/ntchan/nestedcls/OuterCls.outerField:I
    16    #2 = Methodref          #4.#19         // java/lang/Object."<init>":()V
    17    #3 = Class              #20            // com/ntchan/nestedcls/OuterCls
    18    #4 = Class              #21            // java/lang/Object
    19    #5 = Class              #22            // com/ntchan/nestedcls/OuterCls$InnerCls
    20    #6 = Utf8               InnerCls
    21    #7 = Utf8               InnerClasses
    22    #8 = Utf8               outerField
    23    #9 = Utf8               I
    24   #10 = Utf8               <init>
    25   #11 = Utf8               ()V
    26   #12 = Utf8               Code
    27   #13 = Utf8               LineNumberTable
    28   #14 = Utf8               access$000
    29   #15 = Utf8               (Lcom/ntchan/nestedcls/OuterCls;)I
    30   #16 = Utf8               SourceFile
    31   #17 = Utf8               OuterCls.java
    32   #18 = NameAndType        #8:#9          // outerField:I
    33   #19 = NameAndType        #10:#11        // "<init>":()V
    34   #20 = Utf8               com/ntchan/nestedcls/OuterCls
    35   #21 = Utf8               java/lang/Object
    36   #22 = Utf8               com/ntchan/nestedcls/OuterCls$InnerCls
    37 {
    38   public com.ntchan.nestedcls.OuterCls();
    39     descriptor: ()V
    40     flags: (0x0001) ACC_PUBLIC
    41     Code:
    42       stack=2, locals=1, args_size=1
    43          0: aload_0
    44          1: invokespecial #2                  // Method java/lang/Object."<init>":()V
    45          4: aload_0
    46          5: iconst_5
    47          6: putfield      #1                  // Field outerField:I
    48          9: return
    49       LineNumberTable:
    50         line 3: 0
    51         line 4: 4
    52 
    53   static int access$000(com.ntchan.nestedcls.OuterCls);
    54     descriptor: (Lcom/ntchan/nestedcls/OuterCls;)I
    55     flags: (0x1008) ACC_STATIC, ACC_SYNTHETIC
    56     Code:
    57       stack=1, locals=1, args_size=1
    58          0: aload_0
    59          1: getfield      #1                  // Field outerField:I
    60          4: ireturn
    61       LineNumberTable:
    62         line 3: 0
    63 }
    64 SourceFile: "OuterCls.java"
    65 InnerClasses:
    66   #6= #5 of #3;                           // InnerCls=class com/ntchan/nestedcls/OuterCls$InnerCls of class com/ntchan/nestedcls/OuterCls
    View Code

    其中,我们发现OuterCls多了一个静态方法access$000:

    我们看一下这个静态方法做了什么:

    1. 缺省修饰符,表示这个方法的域是包可见
    2. 这个静态方法只有一个参数:OuterCls
    3. ACC_SYNTHETIC:表示这个方法是由编译器自动生成的
    4. aload_0表示把局部变量表的第一个变量加载到操作栈
    5. getfield 访问实例字段 outerField
    6. ireturn 返回传参进来的OuterCls的outerFiled的值

    好像发现了什么,对比代码,我们在内部类使用了外部类的私有域outerField,编译器就自动帮我们生成了一个仅包可见的静态方法来返回outerField的值。

    接着,我们继续查看内部类InnerCls的字节码:

     1 $ javap -verbose OuterCls$InnerCls
     2 Warning: File ./OuterCls$InnerCls.class does not contain class OuterCls$InnerCls
     3 Classfile /Users/ntchan/code/demo/concepts/src/com/ntchan/nestedcls/OuterCls$InnerCls.class
     4   Last modified Aug 14, 2018; size 648 bytes
     5   MD5 checksum 344420034b48389a027a2f303cd2617c
     6   Compiled from "OuterCls.java"
     7 class com.ntchan.nestedcls.OuterCls$InnerCls
     8   minor version: 0
     9   major version: 53
    10   flags: (0x0020) ACC_SUPER
    11   this_class: #6                          // com/ntchan/nestedcls/OuterCls$InnerCls
    12   super_class: #7                         // java/lang/Object
    13   interfaces: 0, fields: 1, methods: 2, attributes: 2
    14 Constant pool:
    15    #1 = Fieldref           #6.#18         // com/ntchan/nestedcls/OuterCls$InnerCls.this$0:Lcom/ntchan/nestedcls/OuterCls;
    16    #2 = Methodref          #7.#19         // java/lang/Object."<init>":()V
    17    #3 = Fieldref           #20.#21        // java/lang/System.out:Ljava/io/PrintStream;
    18    #4 = Methodref          #22.#23        // com/ntchan/nestedcls/OuterCls.access$000:(Lcom/ntchan/nestedcls/OuterCls;)I
    19    #5 = Methodref          #24.#25        // java/io/PrintStream.println:(I)V
    20    #6 = Class              #26            // com/ntchan/nestedcls/OuterCls$InnerCls
    21    #7 = Class              #29            // java/lang/Object
    22    #8 = Utf8               this$0
    23    #9 = Utf8               Lcom/ntchan/nestedcls/OuterCls;
    24   #10 = Utf8               <init>
    25   #11 = Utf8               (Lcom/ntchan/nestedcls/OuterCls;)V
    26   #12 = Utf8               Code
    27   #13 = Utf8               LineNumberTable
    28   #14 = Utf8               printOuterField
    29   #15 = Utf8               ()V
    30   #16 = Utf8               SourceFile
    31   #17 = Utf8               OuterCls.java
    32   #18 = NameAndType        #8:#9          // this$0:Lcom/ntchan/nestedcls/OuterCls;
    33   #19 = NameAndType        #10:#15        // "<init>":()V
    34   #20 = Class              #30            // java/lang/System
    35   #21 = NameAndType        #31:#32        // out:Ljava/io/PrintStream;
    36   #22 = Class              #33            // com/ntchan/nestedcls/OuterCls
    37   #23 = NameAndType        #34:#35        // access$000:(Lcom/ntchan/nestedcls/OuterCls;)I
    38   #24 = Class              #36            // java/io/PrintStream
    39   #25 = NameAndType        #37:#38        // println:(I)V
    40   #26 = Utf8               com/ntchan/nestedcls/OuterCls$InnerCls
    41   #27 = Utf8               InnerCls
    42   #28 = Utf8               InnerClasses
    43   #29 = Utf8               java/lang/Object
    44   #30 = Utf8               java/lang/System
    45   #31 = Utf8               out
    46   #32 = Utf8               Ljava/io/PrintStream;
    47   #33 = Utf8               com/ntchan/nestedcls/OuterCls
    48   #34 = Utf8               access$000
    49   #35 = Utf8               (Lcom/ntchan/nestedcls/OuterCls;)I
    50   #36 = Utf8               java/io/PrintStream
    51   #37 = Utf8               println
    52   #38 = Utf8               (I)V
    53 {
    54   final com.ntchan.nestedcls.OuterCls this$0;
    55     descriptor: Lcom/ntchan/nestedcls/OuterCls;
    56     flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
    57 
    58   com.ntchan.nestedcls.OuterCls$InnerCls(com.ntchan.nestedcls.OuterCls);
    59     descriptor: (Lcom/ntchan/nestedcls/OuterCls;)V
    60     flags: (0x0000)
    61     Code:
    62       stack=2, locals=2, args_size=2
    63          0: aload_0
    64          1: aload_1
    65          2: putfield      #1                  // Field this$0:Lcom/ntchan/nestedcls/OuterCls;
    66          5: aload_0
    67          6: invokespecial #2                  // Method java/lang/Object."<init>":()V
    68          9: return
    69       LineNumberTable:
    70         line 5: 0
    71 
    72   public void printOuterField();
    73     descriptor: ()V
    74     flags: (0x0001) ACC_PUBLIC
    75     Code:
    76       stack=2, locals=1, args_size=1
    77          0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
    78          3: aload_0
    79          4: getfield      #1                  // Field this$0:Lcom/ntchan/nestedcls/OuterCls;
    80          7: invokestatic  #4                  // Method com/ntchan/nestedcls/OuterCls.access$000:(Lcom/ntchan/nestedcls/OuterCls;)I
    81         10: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
    82         13: return
    83       LineNumberTable:
    84         line 7: 0
    85         line 8: 13
    86 }
    87 SourceFile: "OuterCls.java"
    88 InnerClasses:
    89   #27= #6 of #22;                         // InnerCls=class com/ntchan/nestedcls/OuterCls$InnerCls of class com/ntchan/nestedcls/OuterCls
    View Code

    首先,我们发现编译器自动生成了一个声明为final的成员:

    我们知道,final修饰的成员都是编译器可以确定的常量。经过final修饰的变量,都会放到class的常量池。

    然后再看一下编译器自动生成的构造函数:

    具体的字节码指令我就不再一一贴出来,我简单解释一下,这个构造函数通过外部传参OuterCls实例,赋值给this$0(上面那个被final修饰的变量)

    最后看一下我们的printOutField方法:

    我们看到,原本调用outerField的地方,变成了OuterField.access$000(this$0),意思就是,通过OuterField的静态方法,返回this$0的OuterField。

    总的来讲,内部类访问外部类的私有成员的原理,是通过编译器分别给外部类自动生成访问私有成员的静态方法access$000及给内部类自动生成外部类的final引用、外部类初始化的构造函数及修改调用外部类私有成员的代码为调用外部类包可见的access$000实现的。同理,匿名内部类、静态内部类都可以通过这种方法分析实现原理

  • 相关阅读:
    python基础学习-day13==课后作业练习(函数的基本使用)
    python基础学习-函数的基本使用
    python基础学习-day12==课后作业练习(文件指针的控制操作)
    python基础学习-文件其他操作模式(补充)
    python基础学习-day11==课后作业练习(文件操作)
    python基础学习-文件处理
    简单页面设计
    前端.浮动.定位框
    css属性and盒模型
    用html搭建一个注册页面
  • 原文地址:https://www.cnblogs.com/JunFengChan/p/9465036.html
Copyright © 2011-2022 走看看