源碼解釋:
先貼一下String類的申明代碼:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {}
它最大的一個特點是被final修飾了。我們先看看官方怎麼解釋:
Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings. Because String objects are immutable they can be shared.
翻譯如下:
字符串是恆定的,創建之後它們的值不能被改變。StringBuffer是可變的strings.字符串對象不可變讓它們可以被共享。
先認識final類
要了解爲什麼,首先我們得分析一下final修飾類,這個類有什麼特點呢?
從安全上講:
- final的出現就是爲了爲了不想改變
- final 修飾的類是不被能繼承的,所以 final 修飾的類是不能被篡改的(因爲不可能有子類了嘛)
從效率上講:
- 設計成final,JVM才不用對相關方法在虛函數表中查詢,而直接定位到String類的相關方法上,提高了執行效率 這點對提高效率特別重要
- Java設計者認爲共享帶來的效率更高(比如常量池、線程池都是這個概念)
設計者爲什麼讓String被final修飾
首先我們有個共識,String類絕對是被我們使用得最多的一個類,沒有之一。所以它是Java非常底層的一個類,一個數據結構。由於使用得實在太多,所以在設計上做了安全性和效率性的考慮。(Java9在底層存儲結構上都進行了優化,旨在提高效率)
要理解這個問題,需要先了解以下幾點:
- immutable ——不可改變
- 不可改變類——是指類的狀態不變,一旦創建,狀態就是固定不變的
- 字符串池——String pool(常量池,實際上分爲兩種形態:靜態常量池和運行時常量池)
public static void main(String[] args) {
String a = "HELLO";
String b = "HELLO";
String c = new String("HELLO");
String d = new String("HELLO");
System.out.println(a == b); //true 這裏true,字符池的效果體現出來了
System.out.println(b == c); //false
System.out.println(c == d); //false
System.out.println(a.equals(b)); //true equals都會返回true
//intern()方法試用一把
c = c.intern();
System.out.println(b == c); //true 這裏直接也返回true了
}
String pools是爲了提高JAVA內存利用率而採用的措施,當遇到String a = “HELLO”時,JAVA會先在字符串池中查找是否存在“HELLO”這個字符串,如果沒有,則新創建一個對象,然後變量a指向這個地址,然後再遇到String b = “HELLO”時,由於字符串池中以及有了“HELLO”這個對象,所以直接將變量b的地址指向“HELLO”,省去了重新分配的麻煩,如圖
在JAVA中,“==”對於兩個基本類型,判斷內容是否相等,對於對象判斷兩個對象的地址值是否相等
那麼String c = new String(“Hello”)又如何處理呢?
如果是這種寫法,則不會去訪問字符串池,而是先爲變量 c 開闢空間,然後將值寫入空間。所以b == c返回false,c == d同樣返回false。至於String的equals方法,因爲它比較的不是對象的地址,而是對象的值,所以都返回true就不奇怪了。
Java虛擬機有一個字符串池,保存着幾乎所有的字符串對象。字符串表達式總是指向字符串池中的一個對象。使用new操作創建的字符串對象不指向字符串池中的對象
最後一句我們看到,當我們使用intern()方法後,會和池子的對象一樣的效果了。
簡單介紹下intern()方法的原理:如果池中已經有相同的 字符串。有則直接返回池中的字符串,否則先將字符串添加到池中,再返回。這步操作相當於手動向常量池裏扔東西
另外,因爲String是底層的類,且是使用最爲廣泛的類。所以用final修飾,自然而然的方法也會被final修飾。因此在調用String的任何方法的時候,都採用JVM的內嵌機制,效率會有較大的提升
闡述設計成final類的優點
只有當字符串是不可變的,字符串池纔有可能實現
字符串池的實現可以在運行時節約很多heap空間,因爲不同的字符串變量都指向池中的同一個字符串。但如果字符串是可變的,那麼String interning將不能實現(注:String interning是指對不同的字符串僅僅只保存一個,即不會保存多個相同的字符串。),因爲這樣的話,如果變量改變了它的值,那麼其它指向這個值的變量的值也會一起改變。
如果字符串是可變的,那麼會引起很嚴重的安全問題
譬如,數據庫的用戶名、密碼都是以字符串的形式傳入來獲得數據庫的連接,或者在socket編程中,主機名和端口都是以字符串的形式傳入。因爲字符串是不可變的,所以它的值是不可改變的,否則黑客們可以鑽到空子,改變字符串指向的對象的值,造成安全漏洞。
因爲字符串是不可變的,所以是多線程安全的
同一個字符串實例可以被多個線程共享。這樣便不用因爲線程安全問題而使用同步。字符串自己便是線程安全的。
類加載器要用到字符串,不可變性提供了安全性,以便正確的類被加載
譬如你想加載java.sql.Connection類,而這個值被改成了myhacked.Connection,那麼會對你的數據庫造成不可知的破壞。
作爲Map的key,提高了訪問效率
因爲字符串是不可變的,所以在它創建的時候hashcode就被緩存了,不需要重新計算。這就使得字符串很適合作爲Map中的鍵,字符串的處理速度要快過其它的鍵對象。這就是HashMap中的鍵往往都使用字符串。因爲Map使用得也是非常之多,所以一舉兩得