我在跟着[张龙老师]学习JVM 之 静态常量
常量
- 在java 中凡是被 final 所修饰的变量——称为常量
- 常量在编译阶段会存入到调用这个常量的方法所在类的常量池中。
- 本质上,调用类并没有直接引用定义常量的类,因此并不会触发定义常量类的初始化。
案例一(代码):
public class Test1 { public static void main(String[] args) { System.out.println(TestA.name); } } class TestA { public static String name="static TestA"; static { System.out.println("my name is TestA"); } }
案例一(结果):
my name is TestA static TestA
调用 TestA 类的静态变量 会触发 TestA 的初始化,也就导致了 静态块被执行(曾经搞不懂为什么静态块只会被加载一次,现在明白,因为类只会被初始化一次)。最后执行 静态变量 name 的结果
那看看 若 将 name 用 final 修饰会怎样?
案例二(代码):
public class Test1 { public static void main(String[] args) { System.out.println(TestA.name); } } class TestA { public static final String name="static TestA"; static { System.out.println("my name is TestA"); } }
案例二(结果):
static TestA
只是打印了常量 name 的结果,并没有初始化 TestA?
解析说明:
结合上面的话来理解就是,当调用TestA类中常量(name)的时候,在编译阶段会将 常量name 存放到调用该常量的方法(main)所在的类(Test1)的常量池中。换句话说,既然已经存放到了Test1的常量池中,调用常量name 是和TestA这个类是没有关系的。既然没有关系,就肯定不会初始化 TestA 这个类了。
既然没有与TestA 类没有关系,即便把TestA.class文件删除掉都是没有问题的。


依然可以执行。若将 final 去掉呢?

则会重新初始化TestA类并生成一个 TestA.class 文件

反编译:
结合 对 class文件反编译,看看 静态变量 和常量有什么不同(观察红色部分)。
静态变量(代码):
public class Test1 { public static void main(String[] args) { System.out.println(TestA.name); } } class TestA { public static String name="static TestA"; static { System.out.println("my name is TestA"); } }
静态变量(结果):
$ javap -c Test1.class Compiled from "Test1.java" public class com.test.Test1 { public com.test.Test1(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: getstatic #3 // Field com/test/TestA.name:Ljava/lang/String; 6: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 9: return }
常量(代码):
public class Test1 { public static void main(String[] args) { System.out.println(TestA.name); } } class TestA { public static final String name="static TestA"; static { System.out.println("my name is TestA"); } }
常量(结果):
$ javap -c Test1.class Compiled from "Test1.java" public class com.test.Test1 { public com.test.Test1(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #4 // String static TestA 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return }
常量的一些注记符
- ldc 表示将 int 、float、String类型的常量值从常量池中推送至栈顶。 所谓栈顶就是即将要要被使用到的
- bipush 表示将单字节(-128~127)的常量推送至栈顶
- sipush 表示将一个短整型常量指(-32768~32767)推送至栈顶
- iconst_1 表示将 int 类型1推送至栈顶(iconst_0~inconst_5)
- iconst_m1 表示将 int 类型 -1 推送至栈顶
- ldc2_w 表示将double 类型的常量推送至栈顶
- getstatic 表示将静态变量推送至栈顶
- 更多标记符:请看https://www.cnblogs.com/zhangzhonghui/articles/11870661.html
注记符有很多,这也是我第一次接触注记符,注记符远远不止这么一点,我也在上课的是否跟着学习了一了一点,等接触了更多知识,整理一篇博客来讲述注记符。
也接触了反编译,曾经觉得如此的高大上,接触之后才会觉得原来这样啊~
注意:当常量被定义成字面量时才不会导致 该定义常量的类被初始化,换句话说只有编译期间明确知道这个常量值,才能在编译期间讲该常量值放到调用该常量的类的常量池中。
案例(代码):注意红色部分,将name 定义成一个随机的字符串,请问 "my name is TestA" 是否会被执行?
public class Test1 { public static void main(String[] args) { System.out.println(TestA.name); } } class TestA { public static final String name= UUID.randomUUID().toString(); static { System.out.println("my name is TestA"); } }
案例(结果): TestA 类 被初始化了,也就导致 该类的静态块被执行。
my name is TestA
a4e9c7a4-a856-4304-beaf-52fefb7a6e27
反编译的结果:是一个静态变量
$ javap -c Test1.class Compiled from "Test1.java" public class com.test.Test1 { public com.test.Test1(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: getstatic #3 // Field com/test/TestA.name:Ljava/lang/String; 6: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 9: return }
空常量:将常量定义为null
案例(代码):将 常量 name 赋值为null
public class Test1 { public static void main(String[] args) { System.out.println(TestA.name); } } class TestA { public static final String name=null; static { System.out.println("my name is TestA"); } }
案例(结果):TestA 竟然被加载了
my name is TestA null
又把我三观弄塌了,说好和 TestA 没关系了呢(扎心)?难道编译期间不确定 这个 name 的值 为 null?
遇到不懂的问题,就去寻找答案,还好学习反编译,和怎么看反编译的内容,于是将.class文件进行了反编译。
Compiled from "Test1.java" public class com.test.Test1 { public com.test.Test1(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: getstatic #3 // Field com/test/TestA.name:Ljava/lang/String; 6: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 9: return }
请看红色部分,上面我也对静态变量进行了反编译,却发现反编译结果为 静态变量?也只是为什么 TestA 类被初始化了(原来调用的还是一个静态变量,所以导致TestA被初始化)。
问题已经找到了(这里说明一下,定义为空字符串是没有问题的),但是为什么会被当成 静态变量处理呢?明明对 name 进行 final 修饰。
遇到不懂的问题,先思考,再找度娘,看了看别人阐述 null 的。https://blog.csdn.net/zebe1989/article/details/82855631

对于这个答案,我还是有点半信半疑,但是这又是目前比较具有说服力的答案(对于我来说)。既然没有分配内存,又怎么推送到栈顶,进行输出呢?我这是想到一个问题,那么常量池又是怎么存数据的呢?面向百度编程功底还是可以的,紧接着我很快发现了一篇新博客
讲常量结构的时候,是这样的。 https://blog.csdn.net/qq_30137611/article/details/70273291 
其中有一个就是类型,那请问 null 属于什么类型(null 不是一个有效的实例,而是一个特殊的值)?对于这个特性,我是否可以理解 在编译期间是否无法确定 null?
对于这个问题,如果你有什么建议或意见,下方留言。

浙公网安备 33010602011771号