在java中String類為什麼要設計成final?

來源:互聯網
上載者:User

標籤:支援   調用   類型   com   情況   start   tput   csdn   多個   

大神連結:在java中String類為什麼要設計成final? - 程式員 - 知乎

我進行了重新排版,並且更換了其中的一個例子,讓我們更好理解。

 

String很多實用的特性,比如說“不可變性”,是工程師精心設計的藝術品!藝術品易碎!用final就是拒絕繼承,防止世界被熊孩子破壞,維護世界和平!

1. 什麼是不可變?

String不可變很簡單,如,給一個已有字串"abcd"第二次賦值成"abcedl",不是在原記憶體位址上修改資料,而是重新指向一個新對象,新地址。

 

2. String為什麼不可變?

 

翻開JDK源碼,java.lang.String類起手前三行,是這樣寫的:

[java] view plain copy 
  1. public final class String implements java.io.Serializable, Comparable<String>, CharSequence {  
  2.     /** String本質是個char數組. 而且用final關鍵字修飾.*/  
  3.     private final char value[];  
  4.     ...  
  5.     ...  
  6. }  

首先String類是用final關鍵字修飾,這說明String不可繼承。再看下面,String類的主力成員欄位value是個char[ ]數組,而且是用final修飾的。final修飾的欄位建立以後就不可改變。

有的人以為故事就這樣完了,其實沒有。因為雖然value是不可變,也只是value這個引用地址不可變。擋不住Array數組是可變的事實。Array的資料結構看

也就是說Array變數只是stack上的一個引用,數組的本體結構在heap堆。String類裡的value用final修飾,只是說stack裡的這個叫value的引用地址不可變。沒有說堆裡array本身資料不可變。看下面這個例子,

 

[java] view plain copy 
  1. final int[] value={1,2,3}  
  2. int[] another={4,5,6};  
  3. value=another;    //編譯器報錯,final不可變  

 

value用final修飾,編譯器不允許我把value指向堆區另一個地址。但如果我直接對數組元素動手,分分鐘搞定。

[java] view plain copy 
  1. final int[] value={1,2,3};  
  2. value[2]=100;  //這時候數組裡已經是{1,2,100}  

或者更粗暴的反射直接改,也是可以的。

[java] view plain copy 
  1. final int[] array={1,2,3};  
  2. Array.set(array,2,100); //數組也被改成{1,2,100}  

所以String是不可變,關鍵是因為SUN公司的工程師,在後面所有String的方法裡很小心的沒有去動Array裡的元素,沒有暴露內部成員欄位。

private final char value[]這一句裡,private的私人存取權限的作用都比final大。而且設計師還很小心地把整個String設成final禁止繼承,避免被其他人繼承後破壞。所以String是不可變的關鍵都在底層的實現,而不是一個final。考驗的是工程師構造資料類型,封裝資料的功力。

 

3. 不可變有什麼好處?

這個最簡單的原因,就是為了安全

樣本1 [java] view plain copy 
  1. package _12_01字串;  
  2.   
  3. public class 為什麼String要設計成不可變類你 {  
  4.   
  5.     public static void main(String[] args) {  
  6.   
  7.         String a, b, c;  
  8.         a = "test";  
  9.         b = a;  
  10.         c = b;  
  11.         String processA = processA(a);  
  12.         String processB = processB(b);  
  13.         String processC = processC(c);  
  14.         System.out.println(processA);  
  15.         System.out.println(processB);  
  16.         System.out.println(processC);  
  17.     }  
  18.       
  19.     static String processA(String str){  
  20.         return str + "A";  
  21.     }  
  22.       
  23.     static String processB(String str){  
  24.         return str + "B";  
  25.     }  
  26.       
  27.     static String processC(String str){  
  28.         return str + "C";  
  29.     }  
  30.   
  31. }  
  32. //OUTPUT  
  33. // testA  
  34. //testB  
  35. //testC  

當String支援非可變性的時候,它們的值很好確定,不管調用哪個方法,都互不影響。

如果String是可變的,就可能如下例,我們使用StringBuffer來類比String是可變的

[java] view plain copy 
  1. package _12_01字串;  
  2.   
  3. public class 為什麼String要設計成不可變類2 {  
  4.   
  5.     public static void main(String[] args) {  
  6.   
  7.         StringBuffer a, b, c;  
  8.         a = new StringBuffer("test");  
  9.         b = a;  
  10.         c = b;  
  11.         String processA = processA(a);  
  12.         String processB = processB(b);  
  13.         String processC = processC(c);  
  14.         System.out.println(processA);  
  15.         System.out.println(processB);  
  16.         System.out.println(processC);  
  17.     }  
  18.       
  19.     static String processA(StringBuffer str){  
  20.         return str.append("A").toString();  
  21.     }  
  22.       
  23.     static String processB(StringBuffer str){  
  24.         return str.append("B").toString();  
  25.     }  
  26.       
  27.     static String processC(StringBuffer str){  
  28.         return str.append("C").toString();  
  29.     }  
  30.   
  31. }  
  32. //OUTPUT  
  33. // testA  
  34. //testAB  
  35. //testABC  

能看出b=a,c=b;程式員的本意是希望變數是不變的。所以String不可變的安全性就體現在這裡。實際上StringBuffer的作用就是起到了String的可變更配置套類角色。

樣本2

再看下面這個HashSet用StringBuilder做元素的情境,問題就更嚴重了,而且更隱蔽。

[java] view plain copy 
  1. class Test{  
  2.     public static void main(String[] args){  
  3.         HashSet<StringBuilder> hs=new HashSet<StringBuilder>();  
  4.         StringBuilder sb1=new StringBuilder("aaa");  
  5.         StringBuilder sb2=new StringBuilder("aaabbb");  
  6.         hs.add(sb1);  
  7.         hs.add(sb2);    //這時候HashSet裡是{"aaa","aaabbb"}  
  8.   
  9.         StringBuilder sb3=sb1;  
  10.         sb3.append("bbb");  //這時候HashSet裡是{"aaabbb","aaabbb"}  
  11.         System.out.println(hs);  
  12.     }  
  13. }  
  14. //Output:  
  15. //[aaabbb, aaabbb]  

StringBuilder型變數sb1和sb2分別指向了堆內的字面量"aaa"和"aaabbb"。把他們都插入一個HashSet。到這一步沒問題。但如果後面我把變數sb3也指向sb1的地址,再改變sb3的值,因為StringBuilder沒有不可變性的保護,sb3直接在原先"aaa"的地址上改。導致sb1的值也變了。這時候,HashSet上就出現了兩個相等的索引值"aaabbb"。破壞了HashSet索引值的唯一性。所以千萬不要用可變類型做HashMap和HashSet索引值。

 

不可變性支援安全執行緒

 

還有一個大家都知道,就是在並發情境下,多個線程同時讀一個資源,是不會引發竟態條件的。只有對資源做寫操作才有危險。不可變對象不能被寫,所以安全執行緒。

不可變性支援字串常量池

最後別忘了String另外一個字串常量池的屬性。像下面這樣字串onetwo都用字面量"something"賦值。它們其實都指向同一個記憶體位址。

 

[java] view plain copy 
  1. String one = "someString";  
  2. String two = "someString";  


 

這樣在大量使用字串的情況下,可以節省記憶體空間,提高效率。但之所以能實現這個特性,String的不可變性是最基本的一個必要條件。要是記憶體裡字串內容能改來改去,這麼做就完全沒有意義了。

 

轉自:http://blog.csdn.net/u013905744/article/details/52414111

在java中String類為什麼要設計成final?

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.