String str = "hello";
String str2 = new String("hello");
上面两中创建 string对象的方式底层实现的不同之处:
(1)首先,先确定String常量池的定义:
常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。
Java为了提高性能,静态字符串(字面量/常量/常量连接的结果)在常量池中创建,并尽量使用同一个对象,重用静态字符串。
对于重复出现的字符串字面量,JVM会首先在常量池中查找,如果常量池中存在即返回该对象。
(2)str指向在常量池中的字符串"hello",在编译期就已经确定;str2指向堆上的对象,是在运行期创建,而堆中实际存放的字符串还是常量池中"hello",即value属性还是指向常量池中的字符串;
由下面的运行图即可看出,str和str2中value属性的实际地址是相同的,即最终指向的字符串的位置是相同的。
内存模型图入下图所示:
结论:String str2 = new String(“hello”);首先会在堆内存中申请一块内存存储对象内容,同时还会检查字符串常量池中是否含有"hello"字符串,若没有则添加"hello"到字符串常量池中,同时把常量池中的地址放到堆内存中;若有,则直接去常量池中的字符串内存地址即可;然后在栈中创建一个引用str2指向其堆内存块对象。(此过程中可能会创建两个对象,也可能就一个)
关于String对象的一些常见问题
1. 下面这段代码的输出结果是什么?
String a = “hello2”; String b = “hello” + 2; System.out.println((a == b));
输出结果为:true。原因很简单,“hello”+2在编译期间就已经被优化成"hello2",因此在运行期间,变量a和变量b指向的是同一个对象。
2.下面这段代码的输出结果是什么?
String a = “hello2”; String b = “hello”; String c = b + 2; System.out.println((a == c));
输出结果为:false。由于有符号引用的存在,所以 String c = b + 2;不会在编译期间被优化,不会把b+2当做字面常量来处理的,因此这种方式生成的对象事实上是保存在堆上的。因此a和c指向的并不是同一个对象。
3.下面这段代码的输出结果是什么?
String a = “hello2”; final String b = “hello”; String c = b + 2; System.out.println((a == c));
输出结果为:true。对于被final修饰的变量,会在class文件常量池中保存一个副本,也就是说不会通过连接而进行访问,对final变量的访问在编译期间都会直接被替代为真实的值。那么String c = b + 2;在编译期间就会被优化成:String c = “hello” + 2;