從哪看出來String類是不可變的?
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
String類的值是保存在value數組中的,並且是被private final修飾的
- private修飾,表明外部的類是訪問不到value的,同時子類也訪問不到,當然String類不可能有子類,因爲類被final修飾了
- final修飾,表明value的引用是不會被改變的,而value只會在String的構造函數中被初始化,而且並沒有其他方法可以修改value數組中的值,保證了value的引用和值都不會發生變化
所以我們說String類是不可變的。
而很多方法,如substring並不是在原來的String類上進行操作,而是生成了新的String類
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
爲什麼String被設置爲不可變的?
字符串常量池
Java有8種基本數據類型
整數類型:byte,short,int,long。包裝類型爲Byte,Short,Integer,Long
浮點類型:float、double。包裝類型爲Float,Double
字符類型:char。包裝類型爲Character
布爾類型:boolean。包裝類型爲Boolean
8種包裝類型中除了Float,Double沒有實現常量池,剩下的都實現了,當然都是通過享元模式實現的
String類的常量池是在JVM層面實現的。
爲什麼要有常量池?
常量池是爲了避免頻繁的創建和銷燬對象而影響系統性能,其實現了對象的共享。
例如字符串常量池,在編譯階段就把所有的字符串文字放到一個常量池中。
- 節省內存空間:常量池中所有相同的字符串常量被合併,只佔用一個空間。
- 節省運行時間:比較字符串時,== 比equals()快。對於兩個引用變量,只用==判斷引用是否相等,也就可以判斷實際值是否相等。
字符串常量池放在哪?
jdk1.7之前的不討論,從jdk1.7開始,字符串常量池就開始放在堆中,然後本文的所有內容都是基於jdk1.8的
下面這個代碼還是經常被問到的
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = new String("abc");
// true
System.out.println(str1 == str2);
// false
System.out.println(str1 == str3);
// false
System.out.println(str3 == str4);
內存中的結構如下
其中常量池中存的是引用
解釋一下上面代碼的輸出,Java中有2種創建字符串對象的方式
String str1 = "abc";
String str2 = "abc";
// true
System.out.println(str1 == str2);
採用字面值的方式創建一個字符串時,JVM首先會去字符串池中查找是否存在"abc"這個對象
如果不存在,則在字符串池中創建"abc"這個對象,然後將池中"abc"這個對象的地址賦給str1,這樣str1會指向池中"abc"這個字符串對象
如果存在,則不創建任何對象,直接將池中"abc"這個對象的地址返回,賦給str2。因爲str1、str2指向同一個字符串池中的"abc"對象,所以結果爲true。
String str3 = new String("abc");
String str4 = new String("abc");
// false
System.out.println(str3 == str4);
採用new關鍵字新建一個字符串對象時,JVM首先在字符串池中查找有沒有"abc"這個字符串對象,
如果沒有,則首先在字符串池中創建一個"abc"字符串對象,然後再在堆中創建一個"abc"字符串對象,然後將堆中這個"abc"字符串對象的地址賦給str3
如果有,則不在池中再去創建"abc"這個對象了,直接在堆中創建一個"abc"字符串對象,然後將堆中的這個"abc"對象的地址賦給str4。這樣,str4就指向了堆中創建的這個"abc"字符串對象;
因爲str3和str4指向的是不同的字符串對象,結果爲false。
緩存HashCode
String類在被創建的時候,hashcode就被緩存到hash成員變量中,因爲String類是不可變的,所以hashcode是不會改變的。這樣每次想使用hashcode的時候直接取就行了,而不用重新計算,提高了效率
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** Cache the hash code for the string */
private int hash; // Default to 0
}
可以用作HashMap的key
由於String類不可變的特性,所以經常被用作HashMap的key,如果String類是可變的,內容改變,hashCode也會改變,當根據這個key從HashMap中取的時候有可能取不到value,或者取到錯的value
線程安全
不可變對象天生就是線程安全的,這樣可以避免在多線程環境下對String做同步操作
歡迎關注
參考博客
[1]https://mp.weixin.qq.com/s?src=11×tamp=1592637033&ver=2411&signature=alSwI0tpXmnpwBII3UYs1lFZqTk2rx1VymKrT-tfFt86HNwLbLHfMqxloqZhERqHtwi-Ezrx6ksZ-hL19oc0xbqVzXw2yicU77LuqYkFMBlYoBTohvdysefGXMHY6W9l&new=1