更新時間:2020-06-03 16:13:31 來源:動力節點 瀏覽2219次
字符串是不可變的
究竟什么使字符串字面量這么特殊?首先,記住重要的一點是字符串對象是不可變的。
這就意味著一旦創建,一個字符串對象就不能被改變(還是可以通過反射來改變)。
不可變?不能被更改?那怎么解釋這段代碼。
public?class?ImmutableStrings
{
????public?static?void?main(String[]?args)
????{
????????String?start?=?"Hello";
????????String?end?=?start.concat("?World!");
????????System.out.println(end);
????}
}
//?Output
Hello?World!
看這段代碼,字符串被改變了嗎,還是沒有?事實上,這段代碼中并沒有字符串對象被改變。
我們首先將“Hello”賦值給start變量,為了實現這步,需要在堆中創建一個對象,并把它的引用存儲在start中。接下來,我們在這個對象上調用concat(String)方法。進行到這里Java耍了一個小把戲,如果我們查看String的API說明,會發現其中對于concat(String)方法有如下的描述:
方法描述:將指定字符串連接在這個字符串的結尾。
如果長度為0,則返回這個字符串對象。否則就創建一個新的字符串對象,表示這個字符串序列由原字符串對象和參數字符串二者所表示的字符串序列拼接而成。
你肯定看到了,當你將兩個字符串做拼接操作時,實際上并沒有改變原對象,而是直接創建了一個包含原始對象的新的對象,并且將另一個字符串拼在了后面。
我們上面那段代碼就是這么執行的,start變量所引用的字符串對象并沒有改變,如果在調用concat方法之后System.out.println(start);,會發現start仍然指向的是“Hello”。
這時候你可能想到了字符串中的“+”操作符,事實上字符串的“+”操作也是和concat做了同樣的事情(“+”操作實際上是new了一個StringBuilder對象,然后調用append方法)。
字符串的存儲——字符串常量池
你或許聽說過“字符串常量池”這個概念,究竟什么是字符串常量池?有人說是一個字符串對象容器。答案很接近了,但是不完全正確。
事實上他是一用來保存字符串對象引用的容器。
即使字符串是不可變的,它仍然和Java中的其他對象一樣。對象都是創建在堆中,字符串也不例外。
所以字符串常量池仍然依靠堆,他們存儲的只是堆中字符串的引用。
目前還沒有解釋這個池到底是什么,或者它為何存在。
好吧,因為字符串對象是不可變的,所以復制多個引用來“共享”這個字符串是安全的。下面來看一個例子:
public?class?ImmutableStrings
{
????public?static?void?main(String[]?args)
????{
????????String?one?=?"someString";
????????String?two?=?"someString";
????????
????????System.out.println(one.equals(two));
????????System.out.println(one?==?two);
????}
}
//?Output
true
true
在這個例子中,實在沒有必要為一個相同的字符串對象創建兩個實例。如果字符串像StringBuffer一樣是可變的,那么我們會被迫創建兩個對象(如果不這樣做的話,通過一個引用改變它的值,將會導致其他引用的值也同樣改變,從而可能發生錯誤)。
但是,我們知道字符串對象是不能被改變的,我們可以安全地通過兩個引用one和two來使用一個字符串對象。
這個工作是通過字符串常量池完成的,下面來看一下它是如何完成的:
當一個.java文件被編譯成.class文件時,和所有其他常量一樣,每個字符串字面量都通過一種特殊的方式被記錄下來。
當一個.class文件被加載時(注意加載發生在初始化之前),JVM在.class文件中尋找字符串字面量。
當找到一個時,JVM會檢查是否有相等的字符串在常量池中存放了堆中引用。
如果找不到,就會在堆中創建一個對象,然后將它的引用存放在池中的一個常量表中。
一旦一個字符串對象的引用在常量池中被創建,這個字符串在程序中的所有字面量引用都會被常量池中已經存在的那個引用代替。
所以,在上面的例子中字符串常量池中只有一個引用,就是“someString”這個字符串對象的引用。
局部變量one和two都被賦予了同一個字符串對象的引用。可以通過程序的輸出來驗證。
equals方法檢查的是兩個字符串對象是否包含相同的數據(“someString”),而“==”操作符作用在對象上比較的是引用是否相同,這意味著只有兩個引用指向的是同一個對象才會返回true。
所以例子中的兩個引用是相等的。從輸出可以看到,局部變量one和two不僅包含相同的數據,而且還指向相同的對象。
無圖無真相,來看一下他們之間的關系:
注意,對于字符串字面量有一點比較特殊。通過“new”關鍵字構建時一種不同的方式。
下面舉一個例子:
public?class?ImmutableStrings
{
????public?static?void?main(String[]?args)
????{
????????String?one?=?"someString";
????????String?two?=?new?String("someString");
????????
????????System.out.println(one.equals(two));
????????System.out.println(one?==?two);
????}
}
//?Output
true
false
在這個例子中,可以看到由于關鍵字“new”,最后的結果有一點不同。
此例中,兩個字符串字面量仍然被放進了常量池的常量表中,但是當使用“new”時,JVM就會在運行時創建一個新對象,而不是使用常量表中的引用。
雖然例子中的兩個字符串引用所指向的對象包含相同的數據“someString”,但是這兩個對象并不相同。
這一點可以從輸出看出來,equals方法返回了true,而檢查引用是否相等的“==”返回false。
這表明兩個變量指向的是兩個不同的字符串對象。
如果你想看圖形化的表示,下面就是。要記住引用到常量池的字符串對象是在類加載的時候創建的,而另一個對象是在運行時,當“new String”語句被執行時。
如果你想得到兩個引用到相同對象的局部變量,你可以使用String類中的定義的intern()方法。
調用two.intern()后,會在字符串常量池中尋找是否有值相等的對象引用。
如果有的話,就會返回這個引用,然后你可以把它賦給局部變量。
如果這么做的化,局部變量one和two都是同一個對象的引用,并且在字符串常量池中也存有一個引用,就如同第一張圖那樣。這時,在運行時創建的第二個字符串對象將會被GC回收。
以上就是動力節點java培訓機構的小編針對“Java編程技術分享之字符串字面量”的內容進行的回答,希望對大家有所幫助,如有疑問,請在線咨詢,有專業老師隨時為你服務。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習