字符串常量池:字符串常量池在方法区中
为了优化空间,为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池。如果字符串已经存在池中,就返回池中的实例引用。如果字符串不在池中,就会实例化一个字符串并放到池中。Java能够进行这样的优化是因为字符串是不可变的,可以不用担心数据冲突进行共享。所以,在常量池中的这些字符串不会被垃圾收集器回收
1.String str = new String("hello");此时创建的2个对象,但是引用str只指向堆中的对象,并没有指向常量池中的对象
创建了2个对象,1.检查常量池中有没有hello,没有的话,创建对象放到常量池中,再创建对象放到堆中。如果常量池有hello对象,则只创建一个对象并放到堆中。
2.字符串常量池在方法区
3.String str = "hello";此时创建1个对象,引用str指向常量池中的对象
检查常量池有无hello,如果有,则把指向该对象,如果没有,创建对象放在常量池里。
4.intern()方法。把字符串变成常量池里的字符串,即是:把引用指向常量池中的对象
如果常量池中已经包含了等于该String对象的字符串,则返回该字符串。否则,将该String对象加入常量池,并返回引用。
5.String s1 = "tom";
String s2 = "cat";
String s3 = "tomcat";//创建了一个对象,常量池中。
String s4 = "tom"+"cat";//不创建对象,jvm认为带双引号的字符串拼接就是一个整体,并且常量池中已经存在tomcat,故此处不创建对象
String s5 = s1 + s2;//创建一个对象"tomcat"
s3 == s4;true
s3 == s5;false
为什么为false呢?
字符串引用的拼接并不等于带双引号的字符串的拼接,原来两个字符串s1, s2的拼接首先会调用 String.valueOf(obj),这个Obj为s1,而String.valueOf(Obj)中的实现是return obj == null ? "null" : obj.toString(), 然后产生StringBuilder, 调用的StringBuilder(s1)构造方法, 把StringBuilder初始化,长度为str1.length()+16。此时的StringBuilder对象是在堆上创建的!, 接下来调用StringBuilder.append(str2), 把第二个字符串拼接进去, 然后调用StringBuilder.toString返回结果。所以会返回false。
调用了String.valueOf(),产生了stringBuilder,调用它的构造方法,在堆上new出了新对象,然后使用append()方法把第二个字符串s2拼接起来。
实例:
public static void main(String[] args) {
String str1 = new String("abc");
String str2 = "abc";
String str3 = new String("abc");
System.out.println(str1 == str2.intern());
System.out.println(str1 == str3.intern());
System.out.println(str2 == str3.intern());
System.out.println(str2 == str1.intern());
}
结果:false false true true
首先intern()方法:
public String intern()返回字符串对象的规范化表示形式。(这句话到底啥意思我也不太清楚)
当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。 它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
也就是说intern()方法返回的字符串对象肯定是池中的对象而且字符串内容和调用该方法的对象的内容一样。那么结果是false false true true也就不难理解了。str3.intern()和str1.intern()返回的对象就是str2所指向的对象呀。所以我们的结论也得以验证。