目录:

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

一、内部类的定义及用处

内部类(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:

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

  1. 缺省修饰符,表示这个方法的域是包可见
  2. 这个静态方法只有一个参数:OuterCls
  3. ACC_SYNTHETIC:表示这个方法是由编译器自动生成的
  4. aload_0表示把局部变量表的第一个变量加载到操作栈
  5. getfield 访问实例字段 outerField
  6. 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的内部类与外部类之私有域访问的更多相关文章

  1. java:内部类与外部类的区别和联系

    注意事项一:在内部类中可以随意使用外部类的成员方法以及成员变量. 众所周知,在定义成员方法或者成员变量的时候,可以给其加上一些权限的修饰词,以防止其他类的访问.如在成员变量或者成员方法前面,加上Pri ...

  2. 深入理解Java:内部类

    什么是内部类? 内部类是指在一个外部类的内部再定义一个类.内部类作为外部类的一个成员,并且依附于外部类而存在的.内部类可为静态,可用protected和private修饰(而外部类只能使用public ...

  3. 从字节码看java中 this 的隐式传参

    从字节码看java中 this 隐式传参具体体现(和python中的self如出一辙,但是比python中藏得更深),也发现了 static 与 非 static 方法的区别所在! static与非s ...

  4. Java内部类与外部类的那些事

    昨天去笔试的时候遇到了Java的内部类的创建方式与访问权限的问题,我不懂,没写,故今天起来特意去试验一下,就有了这篇总结性的文章. Java中的内部类又分为非静态内部类(匿名内部类也是非静态的内部类) ...

  5. java 内部类与外部类的区别

    最近在看Java相关知识的时候发现Java中同时存在内部类以及非公有类概念,而且这两个类都可以不需要单独的文件编写,可以与其他类共用一个文件.现根据个人总结将两者的异同点总结如下,如有什么不当地方,欢 ...

  6. Java内部类和外部类的通信探索

    1.内部类访问外部类的成员和方法 在内部类中,可以无障碍地访问外部类的所有成员和方法. 在下面的实验代码中,可以看到,内部类sl可以访问外部类的私有成员:sz 和 cur. 同时可以访问私有方法:pr ...

  7. java内部类 和外部类的区别

    java 内部类和静态内部类的区别  详细连接https://www.cnblogs.com/aademeng/articles/6192954.html 下面说一说内部类(Inner Class)和 ...

  8. “全栈2019”Java第七十五章:内部类持有外部类对象

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  9. Java嵌套类,内部类和外部类

    1.嵌套类,内部类 嵌套类是指被定义在一个类内部的类: JAVA的嵌套类有很多种类:1.静态成员类:2.非静态成员类:3.匿名类:4.局部类:其中,除了静态成员类之外,其他的都是内部类,因为静态成员类 ...

随机推荐

  1. Build a pile of Cubes

    version_1: def find_nb(m): # your code ii = 1 total = 0 while total < m: total = sum(each**3 for ...

  2. equals、==、hashCode

    equals和==的区别 ==主要用来比较基本数据类型,而equal主要用来比较对象是否相等.equal是Object的方法. 如果两者都用来比较对象的相等性,那么如果两个引用地址相同,那么==就返回 ...

  3. 史上最全Docker环境安装指南-让安装docker简单到爆

    一.思考❓❔ 1.什么是Docker? 装应用的容器 开发.测试.运维都偏爱的容器化技术 轻量级 扩展性 一次构建.多次分享.随处运行 2.安装Docker难不难? So easy! 此文看过之后,读 ...

  4. Windows 7 上怎样打开SQL Server 配置管理器

    场景 在Windows 7 上打开 SQL Server 的配置管理器. 实现 右击电脑--管理 在计算机管理--服务和应用程序-SQL Server 配置管理器 注: 博客首页: https://b ...

  5. JAVA内存模型与JVM内存结构

    问题:什么事java内存模型? 首先呢不要答堆.栈.方法区.这是JVM的内存结构.下面阐述了JMM和JVM的区别和自己对JMM的见解 1.Java内存模型(JMM):即多线程相关的.定义了一个线程对另 ...

  6. Hbase 日常运维

    日常维护的命令 1,major_compact 'testtable',通常生产环境会关闭自动major_compact(配置文件中hbase.hregion.majorcompaction设 为0) ...

  7. 设置composer镜像地址为阿里云的方法

    所有项目都会使用该镜像地址: composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ 取消配置: ...

  8. 会计的疑惑--BigDecimal的秘密

    为了提供公司的财务信息化,公司A上线了一套自主研发的财务系统,上班第一天,财务C姐就发现了情况不对:几项支出都对,但支出总和一直为0,赶紧向大老板报告.大老板勃然大怒,责令技术部门今天必须解决,小B负 ...

  9. Android 让你的 Room 搭上 RxJava 的顺风车 从重复的代码中解脱出来

    # 什么是 Room ? 谷歌为了帮助开发者解决 Android 架构设计问题,在 Google I/O 2017 发布一套帮助开发者解决 Android 架构设计的方案:Android Archit ...

  10. [python]python的异常处理

    异常处理:首先了解异常,程序出现逻辑错误或者用户输入不合法都会引发异常,而这些异常并不是致命的所以不会导致程序崩溃死掉.可以利用Python提供的异常处理机制在异常出现时及时捕获,并从内部自我消化. ...