博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从字节码的角度看Java内部类与外部类的互相访问
阅读量:4673 次
发布时间:2019-06-09

本文共 5824 字,大约阅读时间需要 19 分钟。

  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 A15     private class A16     {17         private int privateVar;18         int defaultVar;19         protected int protectedVar;20         public int publicVar;21     }22 23     // static inner class B24     private static class B25     {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\testClass\bin>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_015    1:   aload_116    2:   invokespecial   #25; //Method "
":(LTest;)V17 5: return18 19 static void access$1(Test$A, int);20 Code:21 0: aload_022 1: iload_123 2: putfield #29; //Field privateVar:I24 5: return25 26 }

  static内部类B的bytecode如下:

1 E:\workspace\testClass\bin>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_013    1:   invokespecial   #20; //Method "
":()V14 4: return15 16 static void access$1(Test$B, int);17 Code:18 0: aload_019 1: iload_120 2: putfield #23; //Field privateVar:I21 5: return22 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\testClass\bin>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: 5010   Constant pool:11 const #1 = class        #2;     //  Test12 const #2 = Asciz        Test;13 const #3 = class        #4;     //  java/lang/Object14 const #4 = Asciz        java/lang/Object;15 const #5 = Asciz        
;16 const #6 = Asciz ()V;17 const #7 = Asciz Code;18 const #8 = Method #3.#9; // java/lang/Object."
":()V19 const #9 = NameAndType #5:#6;// "
":()V20 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."
":()V27 const #17 = Method #1.#18; // Test.initData:()V28 const #18 = NameAndType #19:#6;// initData:()V29 const #19 = Asciz initData;30 const #20 = Asciz args;31 const #21 = Asciz [Ljava/lang/String;;32 const #22 = class #23; // Test$A33 const #23 = Asciz Test$A;34 const #24 = Method #22.#25; // Test$A."
":(LTest;LTest$A;)V35 const #25 = NameAndType #5:#26;// "
":(LTest;LTest$A;)V36 const #26 = Asciz (LTest;LTest$A;)V;37 const #27 = Method #22.#28; // Test$A.access$1:(LTest$A;I)V38 const #28 = NameAndType #29:#30;// access$1:(LTest$A;I)V39 const #29 = Asciz access$1;40 const #30 = Asciz (LTest$A;I)V;41 const #31 = class #32; // Test$B42 const #32 = Asciz Test$B;43 const #33 = Method #31.#34; // Test$B."
":(LTest$B;)V44 const #34 = NameAndType #5:#35;// "
":(LTest$B;)V45 const #35 = Asciz (LTest$B;)V;46 const #36 = Method #31.#37; // Test$B.access$1:(LTest$B;I)V47 const #37 = NameAndType #29:#38;// access$1:(LTest$B;I)V48 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=159 0: aload_060 1: invokespecial #8; //Method java/lang/Object."
":()V61 4: return62 LineNumberTable:63 line 1: 064 65 LocalVariableTable:66 Start Length Slot Name Signature67 0 5 0 this LTest;68 69 70 public static void main(java.lang.String[]);71 Code:72 Stack=2, Locals=1, Args_size=173 0: new #1; //class Test74 3: dup75 4: invokespecial #16; //Method "
":()V76 7: invokespecial #17; //Method initData:()V77 10: return78 LineNumberTable:79 line 5: 080 line 6: 1081 82 LocalVariableTable:83 Start Length Slot Name Signature84 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的网站上找不着了。。。

 

转载于:https://www.cnblogs.com/zealotrouge/p/3586504.html

你可能感兴趣的文章