一刑桑、面試經(jīng)常會(huì)碰到一個(gè)問(wèn)題措译,就是String不可變
大部分答的時(shí)候會(huì)講因?yàn)镾tring的源碼里面尸昧,它是這樣的
/** The value is used for character storage. */
private final char value[];
它是被final修飾的,被final修飾的真正含義是什么呢来累?一定能講出是不可變砚作,那么到底是什么不可變啊嘹锁?我們可以來(lái)試一試
final int[] value = new int[]{1,2,3};
value = new int[]{1,2,4};
會(huì)報(bào)錯(cuò)
Error:(33, 9) java: 無(wú)法為最終變量value分配值
這說(shuō)明引用不可變偎巢,value不能再指向另一個(gè)變量,但是這能說(shuō)明value的值不可變嗎兼耀?我們?cè)賮?lái)試試看
final int[] value = new int[]{1,2,3};
value[1]=4;
System.out.println(value[0]+" "+value[1]+" "+value[2]);
輸出就是
1 4 3
因此压昼,final不可變指的是引用對(duì)象不可變,而不是對(duì)象的值不可變瘤运,那么到底是什么讓String對(duì)象不可變呢窍霞?再去源碼看看,是不是還有個(gè)private拯坟,這個(gè)讓value的值在外部是不可變的但金。再來(lái)看一個(gè)是如何表現(xiàn)線程安全的不可變的呢:
//現(xiàn)有一個(gè)get方法,在方法內(nèi)改變string
public static String get(String str){
str += "aaa";
System.out.println("get方法中str的hashcode"+str.hashCode());
return str;
}
//測(cè)試
String str = "123";
System.out.println("str的hashcode"+str.hashCode());
System.out.println(get(str));
System.out.println(str);
System.out.println("最后的str的hashcode"+str.hashCode());
//輸出的結(jié)果
str的hashcode48690
get方法中str的hashcode1450620111
123aaa
123
最后的str的hashcode48690
可以發(fā)現(xiàn)get方法其實(shí)形成了一個(gè)新的str郁季,而真正的str并沒有被改變
*這里有個(gè)小tip:String只能通過(guò)hashcode的方式獲得相對(duì)的jvm中的地址冷溃,但并不是真實(shí)的地址。
二梦裂、java中還有個(gè)字符串常量值的概念
先來(lái)看看神奇的地方:
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = s3.intern();
System.out.println(s1==s2);
System.out.println( s2==s3);
System.out.println(s3==s4);
System.out.println(s4==s2);
//輸出
true
false
false
true
s1和s2竟然是一樣的似枕,這就是常量池的作用,你創(chuàng)建一個(gè)常量年柠,它會(huì)先去常量池中尋找是否已經(jīng)存在凿歼,如果存在那就引用同一個(gè),如果不存在那就放進(jìn)去冗恨,通過(guò)new操作符創(chuàng)建的字符串對(duì)象不指向字符串池中的任何對(duì)象答憔,但是可以通過(guò)使用字符串的intern()方法來(lái)指向其中的某一個(gè)。java.lang.String.intern()返回一個(gè)保留池字符串掀抹,就是一個(gè)在全局字符串池中有了一個(gè)入口虐拓。如果以前沒有在全局字符串池中,那么它就會(huì)被添加到里面傲武。
因此蓉驹,使用new創(chuàng)建對(duì)象的時(shí)候是會(huì)創(chuàng)建兩個(gè)對(duì)象或者一個(gè)對(duì)象,它的過(guò)程是先去常量池中尋找是否有谱轨,如果有戒幔,則再堆中(常量池在方法區(qū)中)創(chuàng)建一個(gè)新的對(duì)象并指向它,如果沒有土童,則在常量池中先創(chuàng)建一個(gè)诗茎,再在堆中創(chuàng)建一個(gè),還是指向堆中的,不信咱們來(lái)看看
String s1 = new String("hello");
String s2 = "hello";
System.out.println(s1==s2);
輸出是false敢订,因此使用new創(chuàng)建出來(lái)的必定是指向堆里面的王污。
三、字符串拼接
最經(jīng)典就是+號(hào)問(wèn)題了楚午,那么String s = s1 + s2到底是個(gè)啥昭齐,和String s = "hello"+"world"有啥區(qū)別嗎?其實(shí)他們打印輸出的結(jié)果是一樣的矾柜,只不過(guò)底層的方式是不一樣的
String s = s1 + s2;
這種方式阱驾,是在底層先創(chuàng)建一個(gè)StringBuilder對(duì)象,然后進(jìn)行兩次append操作怪蔑,最后再toString一下輸出里覆,而new一個(gè)StringBuilder對(duì)象是在堆中,所以操作都是在堆中完成的
String s = "hello" + "world";
這種方式缆瓣,編譯的時(shí)候會(huì)認(rèn)為+號(hào)是沒用的喧枷,所以實(shí)際上等同于
String s ="helloworld";
因此當(dāng)問(wèn)到String,StringBuilder弓坞,StringBuffer的問(wèn)題時(shí)隧甚,為啥String做字符串拼接的效率是最低的就有答案了,它會(huì)創(chuàng)建新的對(duì)象放在堆里面渡冻,如果循環(huán)太多而gc來(lái)不及收回戚扳,那么堆中會(huì)被占用大量的空間。
想起來(lái)還有String的內(nèi)容再接著補(bǔ)充吧