从字节码的角度看Java内部类与外部类的互相访问
2014-03-25 11:18
351 查看
Java中non-static内部类为何可以访问外部类的变量?Java中外部类又为何可以访问内部类的private变量?这两个问题困扰过我一段时间,查了一些网上的答案,大多从“闭包”概念入手,理解起来很是费劲,能否从另外一个角度来解释这个问题呢?有句话叫做“真正了不起的程序员应该对每一个字节都了如指掌”,而弄明白Java程序的“每个字节”还是相对容易的,下面就通过一段Java代码的bytecode来分析:
由于Java内部类会编译成单独的.class文件,我们用javap命令反编译每个.class文件来一探究竟。
non-static内部类A的bytecode如下:
static内部类B的bytecode如下:
从bytecode可以很清晰地看出,non-static内部类A的默认构造函数实质上传入了两个参数,第一个是外部类Test对象的引用,并且在内部类A中用final对象来持有这一引用(另一个参数是返回值,A的引用,与本文阐述主题无关),而static内部类B的默认构造函数则没有传入外部类Test对象的引用。这样就回答了第一个问题,non-static内部类是通过隐含传入的外部类对象的引用来完成对外部类的访问的。
再看A和B中均有针对内部类private变量提供了一个access静态方法(注:若没有针对private变量的访问,编译器会把access方法优化掉,所以必须存在外部类访问内部类private变量的代码才有此方法),那么这一方法是否就是外部类可以访问内部类private变量的原因呢?反编译外部类Test的.class文件可以得到:
从进入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的网站上找不着了。。。
public class Test { public static void main(String[] args) { new Test().initData(); } private void initData() { new A().privateVar = 0; new B().privateVar = 0; } // non-static inner class A private class A { private int privateVar; int defaultVar; protected int protectedVar; public int publicVar; } // static inner class B private static class B { private int privateVar; int defaultVar; protected int protectedVar; public int publicVar; } }
由于Java内部类会编译成单独的.class文件,我们用javap命令反编译每个.class文件来一探究竟。
non-static内部类A的bytecode如下:
E:\workspace\testClass\bin>javap -c Test$A Compiled from "Test.java" class Test$A extends java.lang.Object{ int defaultVar; protected int protectedVar; public int publicVar; final Test this$0; Test$A(Test, Test$A); Code: 0: aload_0 1: aload_1 2: invokespecial #25; //Method "<init>":(LTest;)V 5: return static void access$1(Test$A, int); Code: 0: aload_0 1: iload_1 2: putfield #29; //Field privateVar:I 5: return }
static内部类B的bytecode如下:
E:\workspace\testClass\bin>javap -c Test$B Compiled from "Test.java" class Test$B extends java.lang.Object{ int defaultVar; protected int protectedVar; public int publicVar; Test$B(Test$B); Code: 0: aload_0 1: invokespecial #20; //Method "<init>":()V 4: return static void access$1(Test$B, int); Code: 0: aload_0 1: iload_1 2: putfield #23; //Field privateVar:I 5: return }
从bytecode可以很清晰地看出,non-static内部类A的默认构造函数实质上传入了两个参数,第一个是外部类Test对象的引用,并且在内部类A中用final对象来持有这一引用(另一个参数是返回值,A的引用,与本文阐述主题无关),而static内部类B的默认构造函数则没有传入外部类Test对象的引用。这样就回答了第一个问题,non-static内部类是通过隐含传入的外部类对象的引用来完成对外部类的访问的。
再看A和B中均有针对内部类private变量提供了一个access静态方法(注:若没有针对private变量的访问,编译器会把access方法优化掉,所以必须存在外部类访问内部类private变量的代码才有此方法),那么这一方法是否就是外部类可以访问内部类private变量的原因呢?反编译外部类Test的.class文件可以得到:
E:\workspace\testClass\bin>javap -verbose Test Compiled from "Test.java" public class Test extends java.lang.Object SourceFile: "Test.java" InnerClass: #42= #22 of #1; //A=class Test$A of class Test #43= #31 of #1; //B=class Test$B of class Test minor version: 0 major version: 50 Constant pool: const #1 = class #2; // Test const #2 = Asciz Test; const #3 = class #4; // java/lang/Object const #4 = Asciz java/lang/Object; const #5 = Asciz <init>; const #6 = Asciz ()V; const #7 = Asciz Code; const #8 = Method #3.#9; // java/lang/Object."<init>":()V const #9 = NameAndType #5:#6;// "<init>":()V const #10 = Asciz LineNumberTable; const #11 = Asciz LocalVariableTable; const #12 = Asciz this; const #13 = Asciz LTest;; const #14 = Asciz main; const #15 = Asciz ([Ljava/lang/String;)V; const #16 = Method #1.#9; // Test."<init>":()V const #17 = Method #1.#18; // Test.initData:()V const #18 = NameAndType #19:#6;// initData:()V const #19 = Asciz initData; const #20 = Asciz args; const #21 = Asciz [Ljava/lang/String;; const #22 = class #23; // Test$A const #23 = Asciz Test$A; const #24 = Method #22.#25; // Test$A."<init>":(LTest;LTest$A;)V const #25 = NameAndType #5:#26;// "<init>":(LTest;LTest$A;)V const #26 = Asciz (LTest;LTest$A;)V; const #27 = Method #22.#28; // Test$A.access$1:(LTest$A;I)V const #28 = NameAndType #29:#30;// access$1:(LTest$A;I)V const #29 = Asciz access$1; const #30 = Asciz (LTest$A;I)V; const #31 = class #32; // Test$B const #32 = Asciz Test$B; const #33 = Method #31.#34; // Test$B."<init>":(LTest$B;)V const #34 = NameAndType #5:#35;// "<init>":(LTest$B;)V const #35 = Asciz (LTest$B;)V; const #36 = Method #31.#37; // Test$B.access$1:(LTest$B;I)V const #37 = NameAndType #29:#38;// access$1:(LTest$B;I)V const #38 = Asciz (LTest$B;I)V; const #39 = Asciz SourceFile; const #40 = Asciz Test.java; const #41 = Asciz InnerClasses; const #42 = Asciz A; const #43 = Asciz B; { public Test(); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #8; //Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LTest; public static void main(java.lang.String[]); Code: Stack=2, Locals=1, Args_size=1 0: new #1; //class Test 3: dup 4: invokespecial #16; //Method "<init>":()V 7: invokespecial #17; //Method initData:()V 10: return LineNumberTable: line 5: 0 line 6: 10 LocalVariableTable: Start Length Slot Name Signature 0 11 0 args [Ljava/lang/String; }
从进入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的网站上找不着了。。。
相关文章推荐
- 【Java】内部类访问的外部变量定义成final (数据保持一致,外部修改后无法通知内部,编译的时候会通过构造方法传进来) Java内部类一般访问不了外部变量
- Java内部类为什么可以访问外部类的成员
- Java内部类和外部类互相访问问题
- 内部类和外部类之间互相访问
- Java内部类访问外部对象的方法
- 内部类和外部类之间互相访问
- Java内部类访问外部对象为什么必须是final的呢?
- Email配置For Dummies(二):外部访问Exim发信
- win7 访问本机的CentOS 6.3 上的web项目以及 互相通信访问( CentOS运行于本机VMware虚拟机中)
- 走向DBA[MSSQL篇] 从SQL语句的角度 提高数据库的访问性能
- 使用kube-proxy让外部网络访问K8S service的ClusterIP
- 外部程序访问SQL Server
- 两台电脑之间不能互相访问的十个可能原因
- Java内部类的访问规则
- 外部环境访问虚拟主机的服务
- 阿里云服务器Tomcat无法从外部访问
- ADO和ADO.NET的区别(数据访问角度)
- 配置ISA 2006允许内网用户访问外部网络及发布Exchange 2003之一
- asp.net signalR 专题—— 第三篇 如何从外部线程访问 PersistentConnection
- Linux安装Tomcat外部不能访问