Java中String类的经典问题、错误认知以及归纳总结

在学习过程中对String类的理解反复刷新,以此文记之,做归纳总结,也适合新手避坑。

以实用性考虑,环境为Java 8 以及 之后版本。

String类相比其它类特殊的地方在于有一个字符串常量池(StringTable),里面存着字面量的引用

(关于是实例还是引用的争论请看下方链接,个人站引用)

JVM 常量池中存储的是对象还是引用呢?
在这里插入图片描述
达成这个共识以后,我们从最简单的例子开始说起

对象创建

String str = "abc";

"abc"这个String对象是怎么创建的呢?

①在StringTable中查找,若有内容匹配的引用,则直接返回这个引用。若无内容匹配的String实例的引用,则在Java堆里创建一个对应内容的String对象,然后在StringTable记录下这个引用,并返回这个引用。

Java 中new String(“字面量”) 中 “字面量” 是何时进入字符串常量池的?

(感谢此回答,让我受益匪浅)

请留意这个过程,这个和后续提到的intern方法像亲兄弟一样。

入门问题一

String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2);
// 很简单 true
// 内存中只有一个 "hello" 对象被创建,同时被 s1 和 s2 共享。

现在将创建对象变得复杂一点

String str2 = new String(“abc”);

String str2 = new String(“abc”); 与 String str1 = “abc”; 有什么区别?

②因为此行代码构造新字符串要用到字面量"abc",所以要先完成"abc"这个对象的创建(见①),再完成另一个String对象的创建,str2指向后面这个实例。

注意1,我原先写的是“要先创建"abc"这个对象(见①),再new另一个String对象。”后来觉得不严谨,因为"abc"的开始创建比另一个晚,但是完成另一个早,所以换成了上述说法,若要深究,请移步。

深入理解 new String()

注意2,两个实例都在堆中。str2的value指向"abc"的value,两个对象的char[] value是一致的。(JDK9以后是byte[])

这就有了一个经典问题二

String str2 = new String(“hello”); 在内存中创建了几个对象?

由②可知,2个。

结合①和②,很容易解决问题三

String s1 = "javaEE";
String s2 = "javaEE";
String s3 = new String("javaEE");
String s4 = new String("javaEE");
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
System.out.println(s1 == s4);//false
System.out.println(s3 == s4);//false

"+"号拼接字符串

有变量参与的拼接和只有常量参与的拼接是不一样的。

只有常量参与的拼接有以下两种情况

全是字面量拼接

String s2 = "abc" + "123";

final修饰的常量拼接

final String s = "abc";
String s2 = s + "123";

上面均和String s2 = "abc123"是等价的,因此s2的创建过程参考①。

一旦有变量参与拼接,例如

String s = "abc";
String s2 = s1 + "123";

String s2 = new String("abc") + new String("123");

java会通过StringBuilder来进行字符串的拼接,通过append()方法,最后直接toString()返回。而这个toString()方法调用的其实是String(byte[] value, byte coder)这个构造器。注意这个构造器,传入的是字节数组而不是字符串"abc123",就不会创建"abc123"实例并且也不会在StringTable中记录,这意味着"abc123"的引用在StringTable中是找不到的! 接着,既然是调用的构造器,自然是在堆中new一个新对象。

结合上述说明很容易解决问题四和五

问题四

String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = s1 + "world";//s4 字符串内容也 helloworld , s1 是变量, "wo
rld" 常量,变量 + 常量的结果在堆中
String s5 = s1 + s2;//s5 字符串内容也 helloworld , s1 和 s2 都是变量,
变量 + 变量的结果在堆中
String s6 = "hello" + "world";// 常量 + 常量 编译期间就可以确定结果
System.out.println(s3 == s4);//false
System.out.println(s3 == s5);//false
System.out.println(s3 == s6);//true

问题五

final String s1 = "hello";
final String s2 = "world";
String s3 = "helloworld";
String s4 = s1 + "world";//s4 字符串内容也 helloworld , s1 是常量, "wo
rld" 常量,常量 + 常量结果在常量池中
String s5 = s1 + s2;//s5 字符串内容也 helloworld , s1 和 s2 都是常量,
常量 + 常量 结果在常量池中
String s6 = "hello" + "world";// 常量 + 常量 结果在常量池中,因为编译
期间就可以确定结果
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//true
System.out.println(s3 == s6);//true

问题六

String str = "hello";
String str2 = "world";
String str3 ="helloworld";
String str4 = "hello".concat("world");
String str5 = "hello"+"world";
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true

concat 方法也是调用String(byte[] value, byte coder)这个构造器,哪怕是两个常量对象拼接,结果也是在堆。

问题七

String s1 = new String("1") + new String("1");
String s2 = new String("1") + "1";
String s3 = new StringBuilder("1").append("1").toString();
String s4 = "1" + "1";
System.out.println(s1 == s4); //false
System.out.println(s2 == s4);//false
System.out.println(s3 == s4);//false

问题七你做对了,但可能还是有如下误区

误区一

过去我总以为,只要一个字符串对象被new出来,在StringTable中是存在它的字面量引用的,这是错误的。 比如:

String str = String s1 = new String("a") + new String("bc");

此时StringTable中会有"abc"吗?
不会的,StringTable中只有"a"和"bc"。

原因在加号拼接那一节讲过,不再赘述,这里再提,是想重点强调一下,这对后面很重要。

那么问题来了

什么情况下,字符串引用会被存入字符串常量池?

只有下面四种情况

1.字符串字面量:

String s1 = "Hello"; 

这个字符串 “Hello” 引用会被存入字符串常量池。

2.字符串连接(编译时常量):

String s2 = "Hello" + " World"; // 编译器在编译时将其优化为 "Hello World" 

这种情况下,“Hello World” 引用也会存入常量池。

3.常量表达式:

final String prefix = "Hello"; String s4 = prefix + " World"; 
// 由于 prefix 是 final,编译器可以优化并引用到常量池

4.使用 String.intern() 方法

String s3 = new String("Hello").intern(); 

如果常量池中已经存在 "Hello"引用,则返回常量池中的 "Hello"引用;如果不存在,则将当前"Hello"对象的引用添加到常量池中。

前三种之前说过,下面重点解释第四种

先看看注释

When the intern method is invoked, if the pool already contains a
string equal to this String object as determined by the equals(Object)
method, then the string from the pool is returned. Otherwise, this
String object is added to the pool and a reference to this String
object is returned.

若能在StringTable中找到字面量一样的对象引用,返回此引用。若不能找到,将调用对象的引用作为当前字面量的引用存入StringTable中,并返回此引用。

误区二

过去我总以为,调用intern方法时,如果在StringTable中不能找到字面量的引用,会新建一个字面量对象,并把当前对象的引用加入StringTable(眼熟不,这就是字面量String对象的创建过程啊),这是错误的。真实情况是,正如上面所说,不会创建新对象,而是将调用对象的引用作为当前字面量的引用存入StringTable中,并返回此引用。

也正是因为我带着误区二的观点,认为下面的结果是false,但实际上是true。在纠正过来以后,结果是true彻底说通了。

 String a = "a";
    String param = new String("param" + a);
    String intern = param.intern();
    String paramSame = "parama";
    System.out.println(param == paramSame);//true

由此可以轻松解决问题八

String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = (s1 + "world").intern();// 在上一行已经将"helloworld"的引用放入StringTable中了
String s5 = (s1 + s2).intern();
System.out.println(s3 == s4);//true
System.out.println(s3 == s5);//true

问题九

String s1 = new String("1") + new String("1");
s1.intern();
String s2 = "11";//在上一行已经将"11"的引用s1放入StringTable中了
System.out.println(s1 == s2);//true

理解了上文所有的加粗文字后,可以解决知乎上的一众疑惑(对他们问题中的代码结果做出合理解释)

new一个String对象的时候,如果常量池没有相应的字面量真的会去它那里创建一个吗?我表示怀疑。

问题十

String a = "a";
String param = new String("param" + a);
String paramSame = param.intern();
System.out.println(param == paramSame);//true
//调用param.intern();的时候,在StringTable中将param作为"parama"的引用,并且返回param,这两者当然是相等的。

问题十一

String a = "a";
String param = "b" + a;
System.out.println(param.intern() == "ba"); //true
//先调用param.intern(),将param作为"ba"的引用存入StringTable,
//再遇到"ba"时,StringTable中已有"ba"的引用,不再创建新对象,直接返回,此时"ba"的引用是param
System.out.println(param == "ba");//true

问题十二

String a = "a";
String param = "b" + a;
System.out.println("ba" == param.intern()); //true
//StringTable中没有"ba"的引用,会先创建对象,并返回引用。再执行param.intern()时,发现已经有"ba"的引用,直接返回。
//因此相等
System.out.println(param == "ba");//false
//这是两个不同对象的引用

Java 中new String(“字面量”) 中 “字面量” 是何时进入字符串常量池的?

问题十三

 String s1=new String("he")+new String("llo"); 
        s1.intern();   
        String s2="hello";  
        System.out.println(s1==s2);//true

问题十四

 String s1=new String("he")+new String("llo");
        String s2=new String("h")+new String("ello");
        String s3=s1.intern();
        String s4=s2.intern();
        System.out.println(s1==s3);//true
        System.out.println(s1==s4);//true

最后两个问题,我就不解释了,按照上文的规则,都能说得通,可以好好想想,累了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值