一直很好奇
final
的类字段在class文件是怎么表示的,所以用javap看看怎么回事,也顺便复习下字节码指令👿
没有final
修饰的类的静态字段
定义一个类
1 | public class test{ |
编译查看class文件
1 | javac test.java |
你会发现public static String str="严";
直接翻译成一个静态块
1 | static {}; |
上面ldc
的指令就是把常量从常量池读到操作数栈,putstatic
指令从栈顶赋值给类的静态字段str
,这个字段之后在main
函数会读出来。
1 | public static void main(java.lang.String[]); |
getstatic
是用来读取类的静态字段的,这里读出来放入操作数栈。
有final
修饰的类静态字段
改一下这个类,加final
修饰下
1 | public class test{ |
编译查看class文件
1 | javac test.java |
你会发现class文件没有之前的静态块了,而且也不再用getstatic
指令获取字段的值,而是直接ldc
指令取常量池的值。
接下来看看实例字段在加final
或不加会不会有什么不同呢
不加final的实例字段
1 | public class test{ |
类文件
1 | //省略没用的了。。。 |
构造函数里面用ldc
初始化了实例字段str
的值,然后在main函数里面用getfield
指令,获取t
实例的str
的值。
加final
的实例字段
1 | public class test{ |
类文件
1 | //省略没用的了。。。 |
getfield
指令变成了熟悉的ldc
,很明显了,加了final
之后就去常量池去找,就不需要用getfield
指令。
上面的字段是字符串,那接下来看看数字的字段会怎么样呢
数字的非final
静态字段
上代码
1 | public class test{ |
javap结果
1 | //省略常量池 |
基本差不多,只是ldc
换成了bipush
。bipush
就是把后面的操作数(100)压入栈。如果压入的不是100,而是更大或更小,那么用的指令就会不同的了,例如iconst_1
指令,就是压入1到栈。至于main函数里面,还是用getstatic
指令获取字段的值。
数字的final
静态字段
上代码
1 | public class test{ |
javap结果
1 |
|
没啥惊喜的,就是静态块去掉,需要获取字段的值的地方从getstatic
变成了bipush
。
至于实例字段是怎么样的,应该都差不多了,就不列举了,接下来看看字段类型是引用的是怎么样呢。
加final
的引用类型字段
上代码
1 | public class test{ |
javap结果
1 |
|
这里直接上final
的版本,是因为加不加final
,其实都是一样的,都是有静态块,引用的时候不再有什么其他指令了,老老实实的用getstatic
。
总结
对于字符串或者数字类型,他们都是属于字面量,编译时已经知道,他们要么存在常量池里面,要么存在指令的操作数里面,所以加final
标识的情况下,获取值时不用再到关联的对象底下去获取了,因为final
就是不变,直接调用相关指令去常量池,或者直接操作数取就好。
对于引用类型,这个基本要到运行时才能确认他们的引用地址,所以加不加final
都是一样。
看来字节码才是最能确定java是怎么运行的,所以与其找网上的说法不如javap
一下看看。