深入理解String和StringBuilder

时间:2021-12-29 13:31:14

前言

我偶尔会在问答里面看一看谁有问题,如果我知道就回答一下。今天偶然看到一个问题,去掉提问的代码部分,问题就是:

String对象的intern()方法得到的对象,为什么与String对象有时候不相等?

我看到这个问题,我也是懵逼了,今天就写个笔记复习一下。

正文

首先,这个String对象的intern()方法是干什么用的呢?看一下源码的注释:

/**
* 英文注释好长,这里就简单翻译一下
* 返回一个这个String对象的权威代表(请注意,这里返回的是代表,没说返回是自己)
* 有一个字符串池,专门用来维持String对象,当intern方法被调用的时候,返回和他equals方法相同的String对象,如果没有,就把这个String添加到池中,再把这个String对象返回(也就是说这个情况,返回了自己)
*
* s.intern() == t.intern(),只有在s.equals(t)等于true
*/

public native String intern();

看来这个方法和equals关系密切,所以再看一下equals方法:

public boolean equals(Object anObject) {
// 先判断是否是同一个对象
if (this == anObject) {
return true;
}
// 判断是否是String类型
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = count;
// 判断字符串的长度是否相等
if (n == anotherString.count) {
int i = 0;
// 判断每一个位置的字符是否相等
while (n-- != 0) {
if (charAt(i) != anotherString.charAt(i))
return false;
i++;
}
return true;
}
}
return false;
}

equals判断的仅仅的是字符串的内容,所以只要内容相同,在字符串池中都不会重复添加。

结合我们已经对字符串的了解,我们可以总结出一下几点:

1、字符串池中,只包含唯一内容的字符串。
2、字符串池,提供了相同字符串之间的复用机制,防止不同字符串创建多个对象。

这个时候突然想起来刚接触Java时的一个面试题:

String s = “abc” 和 String s = new String(“abc”) 的区别

这个问题大对数都能答对:

String s = “abc” 是先使用字符串池中的abc对象,如果没有创建abc并添加到字符串池中,这个逻辑和intern()方法是完全一样的, 所以这种使用方法也是推荐的使用方法。

String s = new String(“abc”) ,一开始的过程和第一种是一样的,同样是先从字符串池中获取,然后根据情况添加或者返回。但是new操作符,会返回一个新的String对象,也就是说,返回的String对象并不是abc。但是这种方法会出现内存的浪费,所以并不推荐使用。

为了验证我们的想法,我们运行一个小demo:

public class Main {

public static void main(String[] args){
// 注意这里通过创建StringBuilder,已经创建了111,并加入到字符串池
String s1 = new StringBuilder("111").toString();
// 这里还是通过相同的方式,看看是否返回了跟s1相同的对象
String s2 = new StringBuilder("111").toString();
// 直接从字符串池中得到对象
String s3 = "111";
String s4 = "111";

System.out.println(s1.equals(s2));
System.out.println(s1 == s2);

System.out.println(s3.equals(s4));
System.out.println(s3 == s4);
}

}

首先我们使用了两个StringBuilder来拼接字符串,看看得到的结果,然后直接从字符串池中去取,看看得到是不是同一个对象。

true
false
true
true

第一个结果是s1.equals(s2) =true,这个没有疑问,对比内容必然是相同的。
第二个结果是s1 == s2 得到false,说明是s1和s2是相同内容的不同对象。

为什么不是相同对象呢?看一下StringBuilder的toString()方法:

@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}

竟然是一个new String,怪不得对象是不相同的。

第三个结果s3.equals(s4) = true, 这个没有疑问,对比内容必然是相同的。
第四个结果s3 == s4,一样是true,说明得到确实是相同的对象。

总结

有了刚才的验证,我们基本上可以这么理解:

“abc” , 我们可以看做是单例模式,这个abc只创建一次,可以复用。
例如 String s = “abc”, StringBuilder.append(“abc”),实际上使用的都是同一个字符串对象。

并且我们知道了平时使用字符串的几个小细节:

1、String的equals()方法判断的内容相同,不是判断是否是相同对象。
2、StringBuilder的toString()方法,会创建新的字符串对象并返回,这个还是有优化空间的。

我们把之前学到的内容又重新复习了一遍,还找到了StringBuilder性能可以优化的地方,这次复习的收获还是非常惊喜的,最后贴出那个朋友提出的问题:

public static void main(String[] argv){
String a = new StringBuilder(“aa”).append(“计算机”).toString();
System.out.println(a.intern()==a);
String b = new StringBuilder().append(“计算机”).toString();
System.out.println(b.intern()==b);
String c = new String(“dsd”);
System.out.println(c.intern()==c);
}
为何结果是
true
false
false
而不是
false
false
fasle
?

这个问题你能够帮他解答吗?