学以致用,通过字节码理解:Java的内部类与外部类之私有域访问
目录:
- 内部类的定义及用处
- 打开字节码理解内部类
一、内部类的定义及用处
内部类(inner class)是定义在另一个类中的类。使用内部类,我们可以:
- 访问该类定义所在的作用域中的数据,包括私有的数据
- 可以对同一个包中的其他类隐藏起来
- 当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷
本文旨在讲解内部类与外部类可以相互访问对方的私有域的原理,内部类的用法等大家可以自行查阅(官网介绍简单明了:Nested Class);
二、打开字节码理解内部类
我们知道,内部类其实是Java语言的一种语法糖。经过编译会生成一个"外部类名$内部类名.class"的class文件。如下:
非常简单的一个类OuterCls,包含了一个InnerCls内部类。通过javac编译,我们可以看到列表中多了一个文件:OuterCls$InnerCls.class。
接着,我们通过javap -verbose查看生成的OuterCls.class:
- $ javap -verbose OuterCls
- Warning: File ./OuterCls.class does not contain class OuterCls
- Classfile /Users/ntchan/code/demo/concepts/src/com/ntchan/nestedcls/OuterCls.class
- Last modified Aug 14, 2018; size 434 bytes
- MD5 checksum b9a1f41c67c8ae3be427c578ea205d20
- Compiled from "OuterCls.java"
- public class com.ntchan.nestedcls.OuterCls
- minor version: 0
- major version: 53
- flags: (0x0021) ACC_PUBLIC, ACC_SUPER
- this_class: #3 // com/ntchan/nestedcls/OuterCls
- super_class: #4 // java/lang/Object
- interfaces: 0, fields: 1, methods: 2, attributes: 2
- Constant pool:
- #1 = Fieldref #3.#18 // com/ntchan/nestedcls/OuterCls.outerField:I
- #2 = Methodref #4.#19 // java/lang/Object."<init>":()V
- #3 = Class #20 // com/ntchan/nestedcls/OuterCls
- #4 = Class #21 // java/lang/Object
- #5 = Class #22 // com/ntchan/nestedcls/OuterCls$InnerCls
- #6 = Utf8 InnerCls
- #7 = Utf8 InnerClasses
- #8 = Utf8 outerField
- #9 = Utf8 I
- #10 = Utf8 <init>
- #11 = Utf8 ()V
- #12 = Utf8 Code
- #13 = Utf8 LineNumberTable
- #14 = Utf8 access$000
- #15 = Utf8 (Lcom/ntchan/nestedcls/OuterCls;)I
- #16 = Utf8 SourceFile
- #17 = Utf8 OuterCls.java
- #18 = NameAndType #8:#9 // outerField:I
- #19 = NameAndType #10:#11 // "<init>":()V
- #20 = Utf8 com/ntchan/nestedcls/OuterCls
- #21 = Utf8 java/lang/Object
- #22 = Utf8 com/ntchan/nestedcls/OuterCls$InnerCls
- {
- public com.ntchan.nestedcls.OuterCls();
- descriptor: ()V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=1, args_size=1
- 0: aload_0
- 1: invokespecial #2 // Method java/lang/Object."<init>":()V
- 4: aload_0
- 5: iconst_5
- 6: putfield #1 // Field outerField:I
- 9: return
- LineNumberTable:
- line 3: 0
- line 4: 4
- static int access$000(com.ntchan.nestedcls.OuterCls);
- descriptor: (Lcom/ntchan/nestedcls/OuterCls;)I
- flags: (0x1008) ACC_STATIC, ACC_SYNTHETIC
- Code:
- stack=1, locals=1, args_size=1
- 0: aload_0
- 1: getfield #1 // Field outerField:I
- 4: ireturn
- LineNumberTable:
- line 3: 0
- }
- SourceFile: "OuterCls.java"
- InnerClasses:
- #6= #5 of #3; // InnerCls=class com/ntchan/nestedcls/OuterCls$InnerCls of class com/ntchan/nestedcls/OuterCls
其中,我们发现OuterCls多了一个静态方法access$000:
我们看一下这个静态方法做了什么:
- 缺省修饰符,表示这个方法的域是包可见
- 这个静态方法只有一个参数:OuterCls
- ACC_SYNTHETIC:表示这个方法是由编译器自动生成的
- aload_0表示把局部变量表的第一个变量加载到操作栈
- getfield 访问实例字段 outerField
- ireturn 返回传参进来的OuterCls的outerFiled的值
好像发现了什么,对比代码,我们在内部类使用了外部类的私有域outerField,编译器就自动帮我们生成了一个仅包可见的静态方法来返回outerField的值。
接着,我们继续查看内部类InnerCls的字节码:
- $ javap -verbose OuterCls\$InnerCls
- Warning: File ./OuterCls$InnerCls.class does not contain class OuterCls$InnerCls
- Classfile /Users/ntchan/code/demo/concepts/src/com/ntchan/nestedcls/OuterCls$InnerCls.class
- Last modified Aug 14, 2018; size 648 bytes
- MD5 checksum 344420034b48389a027a2f303cd2617c
- Compiled from "OuterCls.java"
- class com.ntchan.nestedcls.OuterCls$InnerCls
- minor version: 0
- major version: 53
- flags: (0x0020) ACC_SUPER
- this_class: #6 // com/ntchan/nestedcls/OuterCls$InnerCls
- super_class: #7 // java/lang/Object
- interfaces: 0, fields: 1, methods: 2, attributes: 2
- Constant pool:
- #1 = Fieldref #6.#18 // com/ntchan/nestedcls/OuterCls$InnerCls.this$0:Lcom/ntchan/nestedcls/OuterCls;
- #2 = Methodref #7.#19 // java/lang/Object."<init>":()V
- #3 = Fieldref #20.#21 // java/lang/System.out:Ljava/io/PrintStream;
- #4 = Methodref #22.#23 // com/ntchan/nestedcls/OuterCls.access$000:(Lcom/ntchan/nestedcls/OuterCls;)I
- #5 = Methodref #24.#25 // java/io/PrintStream.println:(I)V
- #6 = Class #26 // com/ntchan/nestedcls/OuterCls$InnerCls
- #7 = Class #29 // java/lang/Object
- #8 = Utf8 this$0
- #9 = Utf8 Lcom/ntchan/nestedcls/OuterCls;
- #10 = Utf8 <init>
- #11 = Utf8 (Lcom/ntchan/nestedcls/OuterCls;)V
- #12 = Utf8 Code
- #13 = Utf8 LineNumberTable
- #14 = Utf8 printOuterField
- #15 = Utf8 ()V
- #16 = Utf8 SourceFile
- #17 = Utf8 OuterCls.java
- #18 = NameAndType #8:#9 // this$0:Lcom/ntchan/nestedcls/OuterCls;
- #19 = NameAndType #10:#15 // "<init>":()V
- #20 = Class #30 // java/lang/System
- #21 = NameAndType #31:#32 // out:Ljava/io/PrintStream;
- #22 = Class #33 // com/ntchan/nestedcls/OuterCls
- #23 = NameAndType #34:#35 // access$000:(Lcom/ntchan/nestedcls/OuterCls;)I
- #24 = Class #36 // java/io/PrintStream
- #25 = NameAndType #37:#38 // println:(I)V
- #26 = Utf8 com/ntchan/nestedcls/OuterCls$InnerCls
- #27 = Utf8 InnerCls
- #28 = Utf8 InnerClasses
- #29 = Utf8 java/lang/Object
- #30 = Utf8 java/lang/System
- #31 = Utf8 out
- #32 = Utf8 Ljava/io/PrintStream;
- #33 = Utf8 com/ntchan/nestedcls/OuterCls
- #34 = Utf8 access$000
- #35 = Utf8 (Lcom/ntchan/nestedcls/OuterCls;)I
- #36 = Utf8 java/io/PrintStream
- #37 = Utf8 println
- #38 = Utf8 (I)V
- {
- final com.ntchan.nestedcls.OuterCls this$0;
- descriptor: Lcom/ntchan/nestedcls/OuterCls;
- flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
- com.ntchan.nestedcls.OuterCls$InnerCls(com.ntchan.nestedcls.OuterCls);
- descriptor: (Lcom/ntchan/nestedcls/OuterCls;)V
- flags: (0x0000)
- Code:
- stack=2, locals=2, args_size=2
- 0: aload_0
- 1: aload_1
- 2: putfield #1 // Field this$0:Lcom/ntchan/nestedcls/OuterCls;
- 5: aload_0
- 6: invokespecial #2 // Method java/lang/Object."<init>":()V
- 9: return
- LineNumberTable:
- line 5: 0
- public void printOuterField();
- descriptor: ()V
- flags: (0x0001) ACC_PUBLIC
- Code:
- stack=2, locals=1, args_size=1
- 0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
- 3: aload_0
- 4: getfield #1 // Field this$0:Lcom/ntchan/nestedcls/OuterCls;
- 7: invokestatic #4 // Method com/ntchan/nestedcls/OuterCls.access$000:(Lcom/ntchan/nestedcls/OuterCls;)I
- 10: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
- 13: return
- LineNumberTable:
- line 7: 0
- line 8: 13
- }
- SourceFile: "OuterCls.java"
- InnerClasses:
- #27= #6 of #22; // InnerCls=class com/ntchan/nestedcls/OuterCls$InnerCls of class com/ntchan/nestedcls/OuterCls
首先,我们发现编译器自动生成了一个声明为final的成员:
我们知道,final修饰的成员都是编译器可以确定的常量。经过final修饰的变量,都会放到class的常量池。
然后再看一下编译器自动生成的构造函数:
具体的字节码指令我就不再一一贴出来,我简单解释一下,这个构造函数通过外部传参OuterCls实例,赋值给this$0(上面那个被final修饰的变量)
最后看一下我们的printOutField方法:
我们看到,原本调用outerField的地方,变成了OuterField.access$000(this$0),意思就是,通过OuterField的静态方法,返回this$0的OuterField。
总的来讲,内部类访问外部类的私有成员的原理,是通过编译器分别给外部类自动生成访问私有成员的静态方法access$000及给内部类自动生成外部类的final引用、外部类初始化的构造函数及修改调用外部类私有成员的代码为调用外部类包可见的access$000实现的。同理,匿名内部类、静态内部类都可以通过这种方法分析实现原理
学以致用,通过字节码理解:Java的内部类与外部类之私有域访问的更多相关文章
- java:内部类与外部类的区别和联系
注意事项一:在内部类中可以随意使用外部类的成员方法以及成员变量. 众所周知,在定义成员方法或者成员变量的时候,可以给其加上一些权限的修饰词,以防止其他类的访问.如在成员变量或者成员方法前面,加上Pri ...
- 深入理解Java:内部类
什么是内部类? 内部类是指在一个外部类的内部再定义一个类.内部类作为外部类的一个成员,并且依附于外部类而存在的.内部类可为静态,可用protected和private修饰(而外部类只能使用public ...
- 从字节码看java中 this 的隐式传参
从字节码看java中 this 隐式传参具体体现(和python中的self如出一辙,但是比python中藏得更深),也发现了 static 与 非 static 方法的区别所在! static与非s ...
- Java内部类与外部类的那些事
昨天去笔试的时候遇到了Java的内部类的创建方式与访问权限的问题,我不懂,没写,故今天起来特意去试验一下,就有了这篇总结性的文章. Java中的内部类又分为非静态内部类(匿名内部类也是非静态的内部类) ...
- java 内部类与外部类的区别
最近在看Java相关知识的时候发现Java中同时存在内部类以及非公有类概念,而且这两个类都可以不需要单独的文件编写,可以与其他类共用一个文件.现根据个人总结将两者的异同点总结如下,如有什么不当地方,欢 ...
- Java内部类和外部类的通信探索
1.内部类访问外部类的成员和方法 在内部类中,可以无障碍地访问外部类的所有成员和方法. 在下面的实验代码中,可以看到,内部类sl可以访问外部类的私有成员:sz 和 cur. 同时可以访问私有方法:pr ...
- java内部类 和外部类的区别
java 内部类和静态内部类的区别 详细连接https://www.cnblogs.com/aademeng/articles/6192954.html 下面说一说内部类(Inner Class)和 ...
- “全栈2019”Java第七十五章:内部类持有外部类对象
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- Java嵌套类,内部类和外部类
1.嵌套类,内部类 嵌套类是指被定义在一个类内部的类: JAVA的嵌套类有很多种类:1.静态成员类:2.非静态成员类:3.匿名类:4.局部类:其中,除了静态成员类之外,其他的都是内部类,因为静态成员类 ...
随机推荐
- 使用java程序作为celery的工作节点
celery是python实现的分布式调度框架,有时候想用celery去调用java服务,正好有一个celery-java的库可以使用,能达到这个效果,记录一下: 先添加依赖: <depende ...
- python做傅里叶变换
傅里叶变换(fft) 法国科学家傅里叶提出,任何一条周期曲线,无论多么跳跃或不规则,都能表示成一组光滑正弦曲线叠加之和.傅里叶变换即是把一条不规则的曲线拆解成一组光滑正弦曲线的过程. 傅里叶变换的目的 ...
- 三、SpringBoot 整合mybatis 多数据源以及分库分表
前言 说实话,这章本来不打算讲的,因为配置多数据源的网上有很多类似的教程.但是最近因为项目要用到分库分表,所以让我研究一下看怎么实现.我想着上一篇博客讲了多环境的配置,不同的环境调用不同的数据库,那接 ...
- Docker Gitlab CI 部署 Spring Boot 项目
目前在学习这一块的内容,但是可能每个人环境都不同,导致找不到一篇博客能够完全操作下来没有错误的,所以自己也写一下,记录一下整个搭建的过程. Docker 的安装这里就不赘述了,基本上几行命令都可以了, ...
- HTML5实现首页动态视频背景
话不多说,先看效果图: 炫酷吗?你想实现这种动态视频作为背景的首页吗?来,一起来学习,本文将带你一起实现H5动态视频背景: 首先网上找一段清晰的视频下载下来,最好是MP4格式的: 下载好了之后 ...
- 关闭vue的eslint代码检测和WebStorm的代码检测
1. 在vue项目中 bulid > webpack.base.conf.js 中: 如图,在rules规则中有一条规则是校验代码的,也就是红框2那行,要取消可以直接注释掉这行,或者把红框1的函 ...
- 程序员接触新语言————hello world ^-^,web3种样式表
我的第一个网页 <!DOCTYPE html> <html> <head lang="en"> <meta charset="U ...
- python9
v> 软件测试 广州博才科技开发有限公司 迅捷PDF编辑器 3.5 集合 学习目标: 1. 能够说出如何创建集合 2. 能够说出字典和集合的区别 3. 能够说出如何向集合中添加元素 4. 能够说 ...
- WebApi简介
简单创建.NET Core WebApi:https://www.cnblogs.com/yanbigfeg/p/9197375.html 登陆验证四种方式:https://www.cnblogs.c ...
- Mysql 笔记(一)
InnoDB存储引擎 mysql 存储引擎(好难用,看https://www.zybuluo.com/eqyun/note/27850) 简介 InnoDB是事务安全的MySQL存储引擎,从MySQL ...