• 从字节码的角度看Java内部类与外部类的互相访问


      Java中non-static内部类为何可以访问外部类的变量?Java中外部类又为何可以访问内部类的private变量?这两个问题困扰过我一段时间,查了一些网上的答案,大多从“闭包”概念入手,理解起来很是费劲,能否从另外一个角度来解释这个问题呢?有句话叫做“真正了不起的程序员应该对每一个字节都了如指掌”,而弄明白Java程序的“每个字节”还是相对容易的,下面就通过一段Java代码的bytecode来分析:

     1 public class Test
     2 {
     3     public static void main(String[] args)
     4     {
     5         new Test().initData();
     6     }
     7 
     8     private void initData()
     9     {
    10         new A().privateVar = 0;
    11         new B().privateVar = 0;
    12     }
    13 
    14     // non-static inner class A
    15     private class A
    16     {
    17         private int privateVar;
    18         int defaultVar;
    19         protected int protectedVar;
    20         public int publicVar;
    21     }
    22 
    23     // static inner class B
    24     private static class B
    25     {
    26         private int privateVar;
    27         int defaultVar;
    28         protected int protectedVar;
    29         public int publicVar;
    30     }
    31 }

      由于Java内部类会编译成单独的.class文件,我们用javap命令反编译每个.class文件来一探究竟。

      non-static内部类A的bytecode如下:

     1 E:workspace	estClassin>javap -c Test$A
     2 Compiled from "Test.java"
     3 class Test$A extends java.lang.Object{
     4 int defaultVar;
     5 
     6 protected int protectedVar;
     7 
     8 public int publicVar;
     9 
    10 final Test this$0;
    11 
    12 Test$A(Test, Test$A);
    13   Code:
    14    0:   aload_0
    15    1:   aload_1
    16    2:   invokespecial   #25; //Method "<init>":(LTest;)V
    17    5:   return
    18 
    19 static void access$1(Test$A, int);
    20   Code:
    21    0:   aload_0
    22    1:   iload_1
    23    2:   putfield        #29; //Field privateVar:I
    24    5:   return
    25 
    26 }

      static内部类B的bytecode如下:

     1 E:workspace	estClassin>javap -c Test$B
     2 Compiled from "Test.java"
     3 class Test$B extends java.lang.Object{
     4 int defaultVar;
     5 
     6 protected int protectedVar;
     7 
     8 public int publicVar;
     9 
    10 Test$B(Test$B);
    11   Code:
    12    0:   aload_0
    13    1:   invokespecial   #20; //Method "<init>":()V
    14    4:   return
    15 
    16 static void access$1(Test$B, int);
    17   Code:
    18    0:   aload_0
    19    1:   iload_1
    20    2:   putfield        #23; //Field privateVar:I
    21    5:   return
    22 
    23 }

      从bytecode可以很清晰地看出,non-static内部类A的默认构造函数实质上传入了两个参数,第一个是外部类Test对象的引用,并且在内部类A中用final对象来持有这一引用(另一个参数是返回值,A的引用,与本文阐述主题无关),而static内部类B的默认构造函数则没有传入外部类Test对象的引用。这样就回答了第一个问题,non-static内部类是通过隐含传入的外部类对象的引用来完成对外部类的访问的。

      再看A和B中均有针对内部类private变量提供了一个access静态方法(注:若没有针对private变量的访问,编译器会把access方法优化掉,所以必须存在外部类访问内部类private变量的代码才有此方法),那么这一方法是否就是外部类可以访问内部类private变量的原因呢?反编译外部类Test的.class文件可以得到:

     1 E:workspace	estClassin>javap -verbose Test
     2 Compiled from "Test.java"
     3 public class Test extends java.lang.Object
     4   SourceFile: "Test.java"
     5   InnerClass:
     6    #42= #22 of #1; //A=class Test$A of class Test
     7    #43= #31 of #1; //B=class Test$B of class Test
     8   minor version: 0
     9   major version: 50
    10   Constant pool:
    11 const #1 = class        #2;     //  Test
    12 const #2 = Asciz        Test;
    13 const #3 = class        #4;     //  java/lang/Object
    14 const #4 = Asciz        java/lang/Object;
    15 const #5 = Asciz        <init>;
    16 const #6 = Asciz        ()V;
    17 const #7 = Asciz        Code;
    18 const #8 = Method       #3.#9;  //  java/lang/Object."<init>":()V
    19 const #9 = NameAndType  #5:#6;//  "<init>":()V
    20 const #10 = Asciz       LineNumberTable;
    21 const #11 = Asciz       LocalVariableTable;
    22 const #12 = Asciz       this;
    23 const #13 = Asciz       LTest;;
    24 const #14 = Asciz       main;
    25 const #15 = Asciz       ([Ljava/lang/String;)V;
    26 const #16 = Method      #1.#9;  //  Test."<init>":()V
    27 const #17 = Method      #1.#18; //  Test.initData:()V
    28 const #18 = NameAndType #19:#6;//  initData:()V
    29 const #19 = Asciz       initData;
    30 const #20 = Asciz       args;
    31 const #21 = Asciz       [Ljava/lang/String;;
    32 const #22 = class       #23;    //  Test$A
    33 const #23 = Asciz       Test$A;
    34 const #24 = Method      #22.#25;        //  Test$A."<init>":(LTest;LTest$A;)V
    35 const #25 = NameAndType #5:#26;//  "<init>":(LTest;LTest$A;)V
    36 const #26 = Asciz       (LTest;LTest$A;)V;
    37 const #27 = Method      #22.#28;        //  Test$A.access$1:(LTest$A;I)V
    38 const #28 = NameAndType #29:#30;//  access$1:(LTest$A;I)V
    39 const #29 = Asciz       access$1;
    40 const #30 = Asciz       (LTest$A;I)V;
    41 const #31 = class       #32;    //  Test$B
    42 const #32 = Asciz       Test$B;
    43 const #33 = Method      #31.#34;        //  Test$B."<init>":(LTest$B;)V
    44 const #34 = NameAndType #5:#35;//  "<init>":(LTest$B;)V
    45 const #35 = Asciz       (LTest$B;)V;
    46 const #36 = Method      #31.#37;        //  Test$B.access$1:(LTest$B;I)V
    47 const #37 = NameAndType #29:#38;//  access$1:(LTest$B;I)V
    48 const #38 = Asciz       (LTest$B;I)V;
    49 const #39 = Asciz       SourceFile;
    50 const #40 = Asciz       Test.java;
    51 const #41 = Asciz       InnerClasses;
    52 const #42 = Asciz       A;
    53 const #43 = Asciz       B;
    54 
    55 {
    56 public Test();
    57   Code:
    58    Stack=1, Locals=1, Args_size=1
    59    0:   aload_0
    60    1:   invokespecial   #8; //Method java/lang/Object."<init>":()V
    61    4:   return
    62   LineNumberTable:
    63    line 1: 0
    64 
    65   LocalVariableTable:
    66    Start  Length  Slot  Name   Signature
    67    0      5      0    this       LTest;
    68 
    69 
    70 public static void main(java.lang.String[]);
    71   Code:
    72    Stack=2, Locals=1, Args_size=1
    73    0:   new     #1; //class Test
    74    3:   dup
    75    4:   invokespecial   #16; //Method "<init>":()V
    76    7:   invokespecial   #17; //Method initData:()V
    77    10:  return
    78   LineNumberTable:
    79    line 5: 0
    80    line 6: 10
    81 
    82   LocalVariableTable:
    83    Start  Length  Slot  Name   Signature
    84    0      11      0    args       [Ljava/lang/String;
    85 
    86 
    87 }

      从进入initData方法栈后的代码分析,首先是调用了A的<init>方法,这个方法是自动生成的两个方法之一,用于调用A的构造函数(另外一个是<cinit>,用于在虚拟机第一次加载.class时初始化静态变量等),随后访问了A中的private变量,通过.操作符的访问已经被更替为const #27 = Method #22.#28; // Test$A.access$1:(LTest$A;I)V,虽然没法知道#22.#28代表的含义,不过javap已经很人性化地给我们加上了注释,标明这一段就是在调用A中的access方法,而后面$1的含义表示这是A中第一个private变量;对B中private变量的访问大体相同,不再多说。至此,我们已经可以回答第二个问题,外部类是通过内部类隐含的access静态方法来访问其中的private变量的,并没有破坏private修饰符的作用原则。

      另求助一下:哪位仁兄能发一下JVM部分的源码(下载JDK后目录下的src.zip是JDK部分的源码,不是说的这个),以前在sun的官网好像还看到过,现在在oracle的网站上找不着了。。。

  • 相关阅读:
    P3146 [USACO16OPEN]248
    P2590 [ZJOI2008]树的统计
    P3379 【模板】最近公共祖先(LCA)
    P2253 好一个一中腰鼓!
    数组中出现次数超过一半的数字
    字符串的排列
    二叉搜索树与双向链表
    二叉搜索树的后序遍历序列
    从上往下打印二叉树
    顺时针打印矩阵
  • 原文地址:https://www.cnblogs.com/zealotrouge/p/3586504.html
Copyright © 2020-2023  润新知