前言
String是java常用的数据类型之一,我们知道String的对象可能存在于堆区和常量池,然而什么时候会在堆区创建对象,什么时候会取常量池中的对象,这是本文要讨论的问题。
正文
本节通过探索不同的String赋值方式,来讨论jvm如何使用堆区和常量区来创建String对象。
我们使用hotSpot虚拟机,版本为1.8.0_72,使用javap命令对class文件进行反汇编,探索对象创建过程
test1
String a = "hello";
对上述代码进行反汇编为:
其中ldc(load Constant)指令,表示由常量区获取对象,然后将引用存储在astore_1。在这个过程中,只是在常量池创建了常量值,并未在堆区创建对象。
test2
String a = "hello";
String b = a;
对上述代码进行反汇编为:
如上所示,对象a直接赋值给对象b,因此这两个对象地址相等。
test3
String c = new String("hello");
对上述代码进行反汇编为:
如上所示,先在堆区创建String对象,然后再常量池创建了常量值,最后使用该常量值初始化了堆区的String对象。对象c使用的是堆中创建的对象。
test4
String a = “hello”;
String d = a + a;
对上述代码进行反汇编为:
在创建对象d时,先新建了一个StringBuilder对象,使用对象a的值进行初始化,然后再调用append方法再添加了一次a的值。最后使用toString方法反回一个String对象。
test5
String e = “hello” + “hello”;
对上述代码进行反汇编为:
如上所示,现在常量池创建常量hellohello,然后e存储该引用。整个过程中,只在常量池创建了一个对象。
test6
String a = “hello”;
String f = a + “hello”;
对上述代码进行反汇编为:
在创建对象f时,先创建了StringBuilder对象,然后使用对象a的值用于初始化StringBuilder,然后调用append方法添加常量池的"hello"值。最后调用toString方法向对象f赋值。
test7
final String a = "hello";
String g = a + "hello";
对上述代码进行反汇编为:
如上所示,使用final修饰a后,a将作为常量构造g,而不需要再创建StringBuilder对象。
总结
只使用常量对对象进行赋值,只会在常量去创建对象;
使用new关键字创建对象,会在常量区和堆区都创建对象,并且引用为堆区对象;
使用变量对对象进行赋值,会先创建StringBuilder对象,再用append方法添加其他变量,最后使用toString方法创建String对象;
使用final修饰String对象后,该对象将被视为常量。