new String(“abc”)到底创建了几个对象?
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
1.如果字符串常量池中不存在“abc”的引用,则先在字符串常量池中创建,然后在堆空间中创建,总共创建2个字符串对象。
2.如果字符串常量池中存在字符串对象“abc”的引用,则在堆中创建1个字符串对象。
接下来我们通过一些例子理解字符串到底是怎么创建的以及字符串间是否相等:
1.
String s1 = "abc"; // 常量池
String s2 = new String("abc"); // 堆
System.out.println(s1 == s2);
答案:false。s1是字符串常量池的对象,s2是堆中的对象。
String s1 = "a" + "b"; // 常量池
String s2 = "ab"; // 常量池
System.out.println(s1 == s2);
答案:true。s1中由于"a"和"b"都是常量,在编译期就会对其进行优化,等价于"ab",s1和s2都是同一个字符串常量池中的对象。
String s1 = "a" + new String("b"); // new String(),堆
String s2 = "ab"; // 常量池
System.out.println(s1 == s2);
String s1 = new String("a") + new String("b"); // new String(),堆
String s2 = "ab"; // 常量池
System.out.println(s1 == s2);
String x1 = "a"; // 常量池
String x2 = "b"; // 常量池
String s1 = x1 + x2; // new String(),堆
String s2 = "ab"; // 常量池
System.out.println(s1 == s2);
答案:都为false。这三个例子中s1都是动态拼接字符串,等价于new StringBuilder().append(“a”).append(“b”).toString() <=> new String(“ab”),本质上还是new了一个对象,而s2是字符串常量池中的对象,因此不相等。
- jdk1.8版本
String s1 = new String("a") + new String("b"); // 堆
// 此时常量池中有["a","b"],注意并没有"ab",因为动态拼接并不会在常量池中创建对象。
String s2 = s1.intern(); // s2为常量池中的“ab”,由于常量池中没有"ab",创建一个"ab",并且这个"ab"的引用与s1相同。
// 此时常量池中有["a","b","ab"],其中"ab"的引用为s1的引用
System.out.println(s1 == "ab"); // 是同一个引用
System.out.println(s2 == "ab"); // s2就是常量池中的"ab",显然为true
答案:true,true。首先intern方法返回的是常量池中的对象,在jdk1.8中分两种情况:
1.如果s1的字面量在字符串常量池中存在,则直接返回常量池中的对象。
2.如果不存在,在常量池中创建一个对象,该对象保存s1字符串的引用并返回。也就是说此时堆中的s1和常量池中的s1是同一个引用。
5.jdk1.8版本
String x = "ab"; // 常量池
// 此时常量池中有["ab"]
String s1 = new String("a") + new String("b"); // 堆
// 此时常量池中有["a","b","ab"]
String s2 = s1.intern(); // 常量池
// 由于常量池中已经有"ab"了,直接返回常量池中的"ab",此时常量池的"ab"和s1的引用并不是同一个。
System.out.println(s1 == x);
System.out.println(s2 == x);
答案:false, true。intern方法中的第一种情况,s1和x分别来自堆和常量池,且不是同一个引用,为false,s2和x都来自常量池,为true。
6.jdk1.6版本
String x = "ab"; // 常量池
// 此时常量池中有["ab"]
String s1 = new String("a") + new String("b"); // 堆
// 此时常量池中有["a","b","ab"]
String s2 = s1.intern(); // 常量池
// 由于常量池中已经有"ab"了,直接返回常量池中的"ab",此时常量池的"ab"和s1的引用并不是同一个。
System.out.println(s1 == x);
System.out.println(s2 == x);
答案:false,true。
在jdk1.6中,如果字符串常量池有这个字符串,会和1.8一样,直接返回常量池中的字符串,如果字符串常量池中没有这个字符串,会把字符串对象复制一份放入串池,也就是说常量池和堆中的对象引用不是同一个。
7.jdk1.6版本
String s1 = new String("a") + new String("b"); // 堆
// 此时常量池中有["a","b"]
String s2 = s1.intern(); // 常量池
// 此时常量池中有["a","b","ab"]
// 由于1.6版本intern是复制s1,因此s1依旧在堆中,s2在常量池中,不是同一个引用
System.out.println(s1 == "ab");
System.out.println(s2 == "ab");
答案:false,true。
再来看下面这个例子:
String s1 = "Java"; // 常量池
String s2 = s1.intern(); // 常量池
String s3 = new String("Java"); // 堆
String s4 = s3.intern(); // 常量池
System.out.println(s1 == s2); // true
System.out.println(s3 == s4); // false
System.out.println(s1 == s4); // true
s1==s2显然为true,s1 == s4显然也为true,因为s2和s4都是常量池中的"Java",s3和s4不相等,因为s3.intern()中在常量池中已经有"Java"了,直接把常量池中的"Java"返回给s4,而s3不做任何变动。
总结:
- 存放位置
- 字面量(双引号引的)放在字符串常量池中,new出来的对象都放在堆中。
- 字符串拼接
- 如果两个字符串都是直接用双引号引的,拼接时在编译期就会对其进行优化,例如String s = “a” + "b"会直接优化为String s = “ab”,这样创建出来的对象在常量池中。
- 字符串动态拼接时,例如String s = “a” + new String(“b”),String s = new String(“a”) + new String(“b”),String s = x + y,本质上是调用了StringBuilder中的append方法,最后使用toString()转换为String类型,这样创建出来的对象在堆中,并且不会像new String(“abc”)一样去常量池中创建一个字符串。
- intern()方法返回的是常量池中的对象
- 如果是JDK1.8:
- 如果s1的字面量在字符串常量池中存在,则直接返回常量池中的对象。
- 如果不存在,在常量池中创建一个对象,该对象保存s1字符串的引用并返回。也就是说此时堆中的s1和常量池中的s1是同一个引用。
- 可以理解为如果常量池中不存在就将堆中的对象移动到常量池中。
- 如果是JDK1.6:
- 如果s1的字面量在字符串常量池中存在,则直接返回常量池中的对象。
- 如果不存在,在常量池中复制一份s1字符串,s1的引用和常量池中的引用不是同一个。
- 可以理解为如果常量池中不存在就将堆中的对象复制到常量池中。