本文描述在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来进行相应字段的引用。
总结
在整个内部类字节码的生成规则中,主要采用了修改构造函数的方式来将需要在整个内部类中引用的变量进行参数传递。如果你真的想学习java你可以来这个群前面是五二七,中间是四一三后面是一四四,这里有技术大牛亲自指导帮助你 还有免费的直播课程学习,并且,因为是内部类,构造函数是已知的,可以随意的修改。针对特定的场景,可以进行一定的优化,如常量化(临时变量基本类型)。
因为在整个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中final类的简单使用,并对Java关键字的使用做出了总结
定义在一个类内部的类叫内部类,包含内部类的类...内部类可以声明public、protected、private等访问限制,可以声明为abstract的供其他内部类或外部类继承与扩展,或者声明为static、final的,也可以实现特定的接口。
Java中的final关键字
Java基础权限控制与final,内部类,高清完整版,带书签;
带你深入理解Java中的final关键字_动力节点Java学院整理.
Java中final的深度剖析
java内部类,相信很多人都使用过内部类,新建线程使用的匿名内部类,但是有很多人对内部类的一些概念处于模糊阶段,比如为什么方法内部类引用方法参数,方法参数必须设置为final,所以本文系统得整理了一下内部类...
java类集课件,适合新手和大学生,同时也适合自学者。
java中final和static讲解及实例
自己总结的java中final和static的区别,请大家提出宝贵意见。
浅析Java中的final关键字Java开发Java经验技巧共6页.pdf.zip
Java中的final关键字 1、修饰类的成员变量 这是final的主要用途之一,和C/C++的const,即该成员被修饰为常量,意味着不可修改。 上面的代码对age进行初始化后就不可再次赋值,否则编译时会报类似上图的错误。 ...
根据程序上下文环境,Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变:设计或效率。
day11【final、权限、内部类】.pdf
Java编程思想第四版140页是这么描述的,“对应基本类型,final使数值恒定不变,对应对象引用,final使引用恒定不变。 * 一旦引用被初始化指向一个对象,就无法再把他改为指向另一个对象。然而对象其自身却是可以被...
Java实现动态代理的两种方式。 相对来说cglib更加方便。可以实现为实现接口的类(非final类)
Java复习——final.doc
建议通过阅读相关的Java教材或搜索资料,进一步了解与final关键字相关的高级主题,如不可变类和Enum枚举等内容。 如有疑问,可以参考Java官方文档或向技术论坛寻求帮助,进一步加深对final关键字的理解。
Java final关键字的学习demo
java final变量详解 java final变量详解 java final变量详解 java final变量详解