目录:

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

一、内部类的定义及用处

内部类(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. Kubernetes pod 状态

    CrashLoopBackOff: 容器退出,kubelet正在将它重启 InvalidImageName: 无法解析镜像名称 ImageInspectError: 无法校验镜像 ErrImageNe ...

  2. Container容器crontab错误问题

    问题描述 容器中的cron定时计划任务不执行 问题分析 排查一:检查Container容器是否安装cron # rpm -qa | grep cron # ls /etc/init.d/ 若没有安装, ...

  3. SpringCloud(三)Ribbon与Feign

    上一篇使用了Eureka与Ribbon组件做了最简单的的服务注册与发现,我们知道Eureka是实现服务治理中心的组件,但是上一篇Eureka没有实现集群,这样没有保证到Eureka Server的高可 ...

  4. Python集训营45天—Day03

    目录 1. 分支结构 1.1 初步介绍 1.2 使用案例 1.3 练习 2.循环结构 1.1 初步介绍 1.2 使用案例 1. 分支结构 1.1 初步介绍 至今,我们所写的Python代码都是顺序执行 ...

  5. 2、顺序表的实现(java代码)

    1.这里实现了简单的顺序表的,为空判断.是否已满判断,插入.删除,查询元素下标等功能 public class Linear_List { private int[] arr; //用来保存数据 pr ...

  6. python excel to mysql

    import sys import xlrd import pymysql import math import json from collections import OrderedDict # ...

  7. [Linux] linux下vim对于意外退出的文档的再次开启

    转载自博客:https://blog.csdn.net/ljp1919/article/details/48372615 1.对于同一个文件如果上次已经打开,而未关闭的情况下,又打开该文件进行编辑时, ...

  8. LeetCode 430. Faltten a Multilevel Doubly Linked List

    题目链接:LeetCode 430. Faltten a Multilevel Doubly Linked List class Node { public: int val = NULL; Node ...

  9. Flask关于request一些方法和属性的整理(持续更新)

    前提:基于纯后端服务, post 请求 (Content-Type: application/json,) 1.获取未经处理过的原始数据而不管内容类型,如果数据格式是json的,则取得的是json字符 ...

  10. python连接数据库查询

    import sqlite3 as db conn = db.connect(r'D:/data/test.db') print ('Opend database successfully \n') ...