今天在項目中遇到了一個問題,然後我頭鐵的認為一直是bug,結果居然是String引起的,我一直沒有往String這個點上去思考,直到debug之後… …
(菜是原罪呀😣)
不知道你對String了解多少呢? |
一般來說經常用過String的人都會說String是不可變的,你覺得呢? |
這個不可變到底該怎麼了解?是值?位址?還是其他不可變呢? |
= "a";
str = "b";
System.out.println(str);
看上面這一段代碼,你認為它的答案是什麼呢?
如果你認為是aaa,那就不好意思,錯了。
答案:b
如果讓很多人回答,會出現三種不同的意見
(第一種和第三種答案雖相同,但是思想不同)
第一種:b(這一種雖然答案是對的,但還不如答錯的人,因為我估計說這種答案的人沒看過String源碼
第二種:a(這一種答案雖然是錯的,但應該了解過String的源碼,知道String是不可變的
第三種:b(此b非彼b,這種是最正确的,至于為什麼,下面會說)
為什麼會有字元串常量池
因為字元串就是對象,我們都知道配置設定對象需要消耗高昂的時間和空間;另一方面,我們經常會使用字元串;是以為了減少記憶體開銷和提高性能,JVM處理字元串時必須要進行優化。
然後字元串常量池便出現了,當JVM運作時,系統會單獨配置設定一塊空間,這段空間也就是字元串常量池。
當我們建立字元串時,JVM會先檢查字元串常量池是否會有該字元串,如果有,那麼就直接傳回該字元串的引位址;如果沒有,則建立該字元串放到字元串常量池中并傳回位址。
為什麼不可變?
觀看String底層代碼可以看出由于數組使用了final關鍵字進行修飾,導緻該String類的不可變性。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
那麼由于String的不可變性,那麼常量池中肯定不會出現相同的字元串。
不可變的是什麼?
了解了字元串常量池這個應該就很好分析了。
String str = “a”;字元串常量池沒有相應的字元串,是以會在字元串常量池中建立該字元串,然後将位址傳回給str
str = “b”;這一步會發生什麼呢?由于String的不可變性,那麼a是不可能改成b的;是以此時是str這個位址發生了變化。首先會建立b這個字元串,然後将位址傳回給str,是以此時str指向了b;a還是a,還是那片空間,沒有任何變化。
簡單了說,變的隻是引用,和字元串本身的那片空間沒有任何半毛錢關系
從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);
}
//不過開始索引不為0,傳回了一個新的String對象
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
字元串的截取,傳回一個新的String對象
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
//類似上面
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
字元串的拼接(拼接之後copy到一個新的String對象)
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
//copy
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
這樣的方法還有很多很多,我們可以發現其中的特點,如果傳回值類型是String,那麼都是傳回了一個新的對象。另外如果傳回值是其他類型,那麼在該方法中使用該字元串,依舊是先進行複制到一個新的數組中;然後再對其操作。
為什麼要将String設定為不可變性
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
從第一個類的修飾符final可以得出,該類是不可繼承的,也是為了防止其它人繼承該類之後,對其安全性造成破壞。
一般來說操作字元串有三個重要的類,分别是
String、StringBuffer、StringBuilder
差別就不再多說
舉個StringBuffer的例子
= new StringBuffer("aaa");
StringBuffer stringBuffer2 = new StringBuffer("bbb");
List<StringBuffer> list = new ArrayList<>();
list.add(stringBuffer);
list.add(stringBuffer2);
StringBuffer stringBuffer3 = stringBuffer;
stringBuffer3.append("bbb");
System.out.println(list);
[aaabbb, bbb]
通過上面這個例子可以看出,我建立一個list容器;然後向其中加入兩個StringBuffer對象,通過第三個StringBuffer引用改變了容器中的内容,這是不是破壞了安全性。
再看下面這個例子
String str = new String("aaa");
String str2 = new String("bbb");
List<String> list = new ArrayList<>();
list.add(str);
list.add(str2);
String str3 = str;
str3 += "bbb";
System.out.println(list);