前天晚上,我在一个秋招的交流群里看到了一个群友发的一个问题,顿时就引起来我的兴趣,是这段代码
public class StaticTest1 {
public static final String A;
public static final String B;
static {
A = "ab";
B = "cd";
}
public static void main(String[] args) {
String s = A + B;
String t = "abcd";
if (s == t) {
System.out.println("s等于t,它们是同一个对象");
} else {
System.out.println("s不等于t,它们不是同一个对象");
}
}
}
看到后,我立即把代码敲到了IDE里面,运行之前,我想了一下,被static和final同时修饰的字符串需要在编译时候就要被确定下来,正好在static代码块中有两句赋值语句,所以字符串s和字符串t应该指向同一个实例对象。分析完毕,信心十足地运行了代码,结果却是

这时候看到一个群友说代码修改一下,于是改成了下面这个样子
public class StaticTest2 {
public static final String A = "ab";
public static final String B = "cd";
static {}
public static void main(String[] args) {
String s = A + B;
String t = "abcd";
if (s == t) {
System.out.println("s等于t,它们是同一个对象");
} else {
System.out.println("s不等于t,它们不是同一个对象");
}
}
}
区别很明显,字符串A和B在声明的时候就已经被赋值了,按道理来说,这个应该和之前的分析一致,这个结果应该是s和t是同一个对象。运行之后,果真,控制台窗口出现了“s等于t,它们是同一个对象”字样。
这个就很奇怪,第一次的运行结果没有按照分析来走。想一想,肯定是分析有差错,但是这些字面上的代码怎么能分析出来呢,现在要的方法只能是将这些文件编译后得到的class文件反编译一下,来看看JVM是怎么处理这些代码的。
经过努力,在我浩如烟海的软件文件夹中找到了jad,这是个命令行工具,将jad.exe文件配置到环境变量里面后就可以直接使用了。
首先,我们要得到这两个文件对应的class文件,要运用到javac命令。这里注意一下,如果代码中有中文字符,命令行里编译可能会报错,javac命令要带上-encoding utf-8参数。

下面展示的主要的区别地方

对于StaticTest2.java来说,这个正如我最开始说的那样,JVM会在编译时期就将A+B的值优化为“abcd",这是因为A和B在程序运行就存到了class文件的常量池中,编译器就可以直接从常量池中取出变量来做优化。放在静态代码块里面就已经不是常量,它在编译的时候不会出现在class文件的常量池里面。
class文件头部有一个constant pool,里面存着编译器生成的各种字面量常量,可以通过javap命令查看class文件的字面量。


这次对代码的剖析自己感觉还是没有完全说明白为什么JVM会这样子处理,对JVM也了解的不够深入,不过这次群友的热烈讨论还是让自己学到了很多知识,在这里谢谢大家。