Java内部类final语义实现
本文描述在java内部类中,经常会引用外部类的变量信息。但是这些变量信息是如何传递给内部类的,在表面上并没有相应的线索。本文从字节码层描述在内部类中是如何实现这些语义的。
本地临时变量 基本类型
final int x = 10;
new Runnable() {
@Override
public void run() {
System.out.println(x);
}
}.run();
当输出内部类字节码(javap -p -s -c -v)时,如下所示:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: bipush 10 5: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 8: return
可以看出,此常量值直接被写在内部类的临时变量中,即相当于进行了一次变量copy。
本地临时变量 引用类型
final T t = new T();
new Runnable() {
@Override
public void run() {
System.out.println(t);
}
}.run();
字节码变为如下所示:
final T val$t;
flags: ACC_FINAL, ACC_SYNTHETIC
T$1(T);
Signature: (LT;)V
//构建函数的字节码
0: aload_0
1: aload_1
2: putfield #1 // Field val$t:LT;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
//main函数字节码
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #1 // Field val$t:LT;
7: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
10: return
可以看出,这时自动生成了一个带有1个参数的构造函数,并且将相应的t值作为参数传递到内部类当中,同时设定final语义,即不能被内部类修改。
上面的是无参构造函数,如果是一个有参数的内部类呢,如下所示:
Thread thread = new Thread("thread-1") {
@Override
public void run() {
System.out.println(t);
}
};
生成的字节码如下:
T$1(java.lang.String, T); Signature: (Ljava/lang/String;LT;)V
可以看出,编译器将自动对原来调用的构造函数进行了修改,将原来只需要1个参数的构造函数 修改为传2个参数,并且同时将相应的t传递进去。
引用字段,基本类型
int t = 3;
private void xx() {
new Runnable() {
@Override
public void run() {
System.out.println(t);
}
}.run();
}
生成的字节码如下:
T$1(T);
Signature: (LT;)V
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LT;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
这里并没有如临时变量那样,直接在内部类中进行常量定义。为什么?因为这里的t对象随时可能被修改。
引用字段,引用类型
final String t = new String("abc");
private void xx() {
new Runnable() {
@Override
public void run() {
System.out.println(t);
}
}.run();
}
生成字节码如下:
final T this$0;
Signature: LT;
T$1(T);
//内部类构造函数
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LT;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
这里,在内部类的构造函数中,直接将外部类的this传递进来了,因此在内部类的run方法中,对于t,将直接两层getField进行调用,即可以拿到相应的信息。如下所示:
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_0 4: getfield #1 // Field this$0:LT; 7: getfield #4 // Field T.t:Ljava/lang/String; 10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 13: return
引用类型,引用类型,static字段
static String t = new String("abc");
private void xx() {
new Runnable() {
@Override
public void run() {
System.out.println(t);
}
}.run();
}
字节码如下:
final T this$0;
Signature: LT;
flags: ACC_FINAL, ACC_SYNTHETIC
T$1(T);
Signature: (LT;)V
//构造函数字节码
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LT;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
//run方法字节码
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #4 // Field T.t:Ljava/lang/String;
6: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
9: return
可以看出,即使是引用static字段,在内部类中仍然会保留外部类的引用,即达到引用目的。同时,在run方法内部,因为是static字段,因此将不再使用getField,而是使用getStatic来进行相应字段的引用。
总结
在整个内部类字节码的生成规则中,主要采用了修改构造函数的方式来将需要在整个内部类中引用的变量进行参数传递。并且,因为是内部类,构造函数是已知的,可以随意的修改。针对特定的场景,可以进行一定的优化,如常量化(临时变量基本类型)。
因为在整个JVM层,并没有针对内部类作特殊的处理,因此这些处理手法都是在编译层进行处理的。同时,在语言层,针对这些生成的信息进行指定的说明。如SYNTHETIC语义。
在反射字段Member层,定义了如下方法:
/**
* Returns {@code true} if this member was introduced by
* the compiler; returns {@code false} otherwise.
*
* @return true if and only if this member was introduced by
* the compiler.
* @jls 13.1 The Form of a Binary
* @since 1.5
*/
public boolean isSynthetic();
即此信息是由编译器引入的。
了解这些对于整个语言层有一定的理解意义,但并不代表将来这些不会会改变,了解一些实现细节有助于自己在代码实现层有进一步的思考空间,并不局限于之前所了解的信息。
学习Java的同学注意了!!!
学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:454297367 我们一起学Java!
Java内部类final语义实现的更多相关文章
- 为什么java内部类访问局部变量必须声明为final?
https://blog.csdn.net/z55887/article/details/49229491 先抛出让我疑惑了很久的一个问题 编程时,在线程中使用局部变量时候经常编译器会提示:局部变量必 ...
- Java内部类详解
Java内部类详解 说起内部类这个词,想必很多人都不陌生,但是又会觉得不熟悉.原因是平时编写代码时可能用到的场景不多,用得最多的是在有事件监听的情况下,并且即使用到也很少去总结内部类的用法.今天我们就 ...
- 黑马----JAVA内部类
黑马程序员:Java培训.Android培训.iOS培训..Net培训 黑马程序员--JAVA内部类 一.内部类分为显式内部类和匿名内部类. 二.显式内部类 1.即显式声明的内部类,它有类名. 2.显 ...
- java 内部类 *** 最爱那水货
注: 转载于http://blog.csdn.net/jiangxinyu/article/details/8177326 Java语言允许在类中再定义类,这种在其它类内部定义的类就叫内部类.内部类又 ...
- java内部类和匿名内部类
内部类即是包含在类里面的又一个类. java内部类分为: 成员内部类.静态嵌套类.方法内部类.匿名内部类 . 内部类的共性 (1).内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.clas ...
- Java内部类小程序(成员内部类,静态内部类,匿名内部类)
/** * 测试java内部类(成员内部类,静态内部类,匿名内部类) * 局部内部类不常用,就不写了. * @package :java05 * @author shaobn * @Describe ...
- [转] Java内部类详解
作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置 ...
- java内部类的作用分析
提起Java内部类(Inner Class)可能很多人不太熟悉,实际上类似的概念在C++里也有,那就是嵌套类(Nested Class),关于这两者的区别与联系,在下文中会有对比.内部类从表面上看,就 ...
- JAVA内部类(转)
源出处:JAVA内部类 在java语言中,有一种类叫做内部类(inner class),也称为嵌入类(nested class),它是定义在其他类的内部.内部类作为其外部类的一个成员,与其他成员一样, ...
随机推荐
- WebApi返回Json格式字符串
WebApi返回json格式字符串, 在网上能找到好几种方法, 其中有三种普遍的方法, 但是感觉都不怎么好. 先贴一下, 网上给的常用方法吧. 方法一:(改配置法) 找到Global.asax文件,在 ...
- 在 SharePoint Server 2016 本地环境中设置 OneDrive for Business
建议补丁 建议在sharepoint2016打上KB3127940补丁,补丁下载地址 https://support.microsoft.com/zh-cn/kb/3127940 当然不打,也可以用O ...
- C#事件-使用事件需要的步骤
事件是C#中另一高级概念,使用方法和委托相关.奥运会参加百米的田径运动员听到枪声,比赛立即进行.其中枪声是事件,而运动员比赛就是这个事件发生后的动作.不参加该项比赛的人对枪声没有反应. 从程序员的角度 ...
- OpenWrt中开启usb存储和samba服务
在从官网安装的WNDR3800 15.05.1版本OpenWrt中, 不带usb存储支持以及samba, 需要另外安装 1. 启用usb支持 USB Basic Support https://wik ...
- [转载]Java 8 日期&时间 API
Java 8 日期和时间 声明 本文转自http://www.journaldev.com/2800/java-8-date-localdate-localdatetime-instant,以mark ...
- ASP.NET 中的 Async/Await 简介
本文转载自MSDN 作者:Stephen Cleary 原文地址:https://msdn.microsoft.com/en-us/magazine/dn802603.aspx 大多数有关 async ...
- Mono 3.2.3 Socket功能迎来一稳定的版本
由于兴趣自己业余时间一直在搞.net下面的通讯应用,mono的存在得以让.NET程序轻松运行在Linux之下.不过经过多尝试Socket相关功能在Mono下的表现并不理想.不管性能还是吞吐能力方面离我 ...
- 让Mono 4在Raspberry Pi上飞
最近公司有项目想要在树莓派上做,代替原来的工控机(我们是把工控主机当作小的主机用,一台小的工控主机最少也要600左右,而树莓派只要200多).于是,公司买了一个Raspberry Pi B+和一个Ra ...
- Unity3D新手引导开发手记
最近开始接手新手引导的开发,记录下这块相关的心得 首先客户端是Unity,在接手前,前面的同学已经初步完成了新手引导框架的搭建,这套框架比较简单,有优点也有缺点,稍后一一点评 我们的新手引导是由一个个 ...
- Sass用法指南
写在前面的话:随着CSS文件越来越大,内容越来越复杂,对其进行很好的维护将变的很困难.这时CSS预处理器就能够帮上大忙了,它们往往拥有变量.嵌套.继承等许多CSS不具备的特性.有很多CSS预处理器,这 ...