1. 程式人生 > >java裏String類為何被設計為final

java裏String類為何被設計為final

使用 hashtable 方向 memory 思維 per 垃圾收集器 其他 tro

  前些天面試遇到一個非常難的關於String的問題,“String為何被設計為不可變的”?類似的問題也有“String為何被設計為final?”個人認為還是前面一種問法更準確,設計成final僅僅保證了String類不能被繼承,而Immutable相對於final要嚴格的多。

  下文主要翻譯自:http://java67.blogspot.sg/2014/01/why-string-class-has-made-immutable-or-final-java.html

  要回答這個問題,java程序員必須對String是如何工作的,它的特性是什麽,還有一些關鍵原則有一個很深刻的理解。String類在Java裏是一個上帝類,它有一些其他類所不具備的特性,比如String字面量存儲在常量池,你可以通過操作符“+”來連接多個String。鑒於String類在Java編程裏的重要性,Java設計者把它設計為final的,這意味著你不可以繼承這個類,同樣這也有助於String對象的不可變。
  記得在某個地方讀到過,有人向Java的創建者James Gosling問過為什麽要把String類設計成final,但他在安全性方面作了一些回答。有人認為,使類設計成final嚴重限制了它的進化或擴展能力,James評論說,把類設計成final是Java安全性承諾的關鍵因素,這樣在Java平臺裏就沒有人可以改變它的行為了。     現在回到標題的問題,Java裏String為什麽是不可變的呢?首先可以確定的是這麽設計是有優勢的。現在,讓我們思考一下這些優點或特性,這是決定這樣設計的原因。
    下面列出5個Java裏把String設計為final或者Immutable的原因:   除了JamesGosling關於安全性的提示之外,我認為以下原因也說明了為什麽String在Java中設計成final或Immutable的
  1)String常量池 java設計者明白String類將會是所有Java應用中使用最多的類,這也是他們想從設計之初就要優化的原因。優化方向的一個關鍵想法是在String常量池中存儲String字面量。目標是通過共享來減少臨時字符串對象,為了共享,String類必須是不可變的。你不能在一個彼此不互知的雙方之間共享可變對象。讓我們以一個假設的例子為例,其中兩個引用變量指向同一個字符串對象: String s1 = "Java"; String s2 = "Java";   現在假如s1的值被改成“C++”,引用變量s2在它不知道的情況下,他的值也變成了“C++”。但是通過把String設計為不可變,上述的共享String字面量成為了可能。總之,要在Java中實現字符串池的關鍵思想,必須把String類設計成不可變的。
  2) 安全性   Java在為每個級別的服務提供安全環境方面有著明確的目標,而字符串在整個安全方面是至關重要的。 String已被廣泛用作許多Java類的參數,例如,打開網絡連接時,可以將主機和端口作為字符串傳遞,在Java中讀取文件時,可以將文件和目錄的路徑作為字符串傳遞,打開數據庫連接時,可以將數據庫URL作為字符串傳遞。如果字符串不是不可變的,用戶可能已經授權訪問系統中的特定文件,但是在身份驗證之後,他可以更改到其他文件的路徑,這可能會導致嚴重的安全問題。類似地,在連接到數據庫或網絡中的任何其他機器時,可變字符串值可能會造成安全威脅。可變字符串也可能導致反射中的安全性問題,因為參數是字符串。   3) 字符串在類加載機制中的應用   另一個使字符串成為最終或不可變的原因是因為它在類加載機制中被大量使用。由於字符串不是不可變的,攻擊者可以利用這一事實,將加載標準Java類(例如java.io.Reader)的請求更改為惡意類com.un未知n.DataStolenReader。通過保持字符串的最終性和不可變性,我們至少可以確保JVM正在加載正確的類。   4) 多線程好處   由於並發性和多線程是Java的關鍵產品,因此考慮字符串對象的線程安全性是很有意義的。由於預期字符串將被廣泛使用,使其不可變意味著沒有額外的同步,因此意味著在多個線程之間共享字符串的代碼要簡單得多。這個單一的特性使得已經很復雜、混亂和容易出錯的並發編碼變得更加容易。因為字符串是不可變的,而且我們只是在線程之間共享它,所以它會產生更易讀的代碼。      5) 優化與性能   現在,當你使類不可變時,您預先知道,這個類一旦創建就不會改變。這保證了許多性能優化的開放思維,例如緩存。字符串本身知道,我不會更改,所以字符串緩存它的hashcode。它甚至延遲計算hashcode,一旦創建,只需緩存它。在簡單的世界中,當您第一次調用任何String對象的hashCode()方法時,它計算哈希代碼,然後對hashCode()的所有後續調用都返回已經計算的緩存值。這將帶來良好的性能增益,因為字符串在基於哈希的映射(例如Hashtable和HashMap)中大量使用。如果不使其不可變,就不可能緩存哈希代碼,因為它取決於字符串本身的內容。   除了上述優點外,您還可以考慮到在Java中字符串是immutable的優點。它是最流行的對象之一,可用作基於哈希集合的鍵,例如HashMap和Hashtable。雖然對HashMap鍵來說,不可變性不是絕對需要的,但使用不可變對象作為鍵要比使用可變對象安全得多,因為如果可變對象的狀態在HashMap中停留期間發生了更改,那麽就不可能檢索回它,因為它的Eques()和hashCode()方法依賴於已更改的屬性。如果一個類是不可變的,那麽當它存儲在基於哈希的集合中時,就沒有改變它的狀態的風險,我已經強調的另一個重要的好處是它的線程安全性。由於字符串是不可變的,所以您可以在線程之間安全地共享它,而不必擔心額外同步。它使並發代碼更具可讀性,減少了錯誤的發生。      盡管有所有這些優點,但不變性也有一些缺點,例如,它並非沒有成本。由於字符串是不可變的,因此它會生成大量臨時對象和逃逸對象,這會給垃圾收集器帶來壓力。Java設計者已經考慮過了,將字符串字面量存儲在常量池中是他們減少字符串垃圾的解決方案。這確實有幫助,但是你必須小心地創建字符串而不使用構造函數,例如,new String()不會從字符串池中選擇對象。而且,平均來說,Java應用程序產生的垃圾太多了。此外,將字符串存儲在池中有與其相關的隱藏風險。字符串池位於JavaHeap的PermGen空間中,與JavaHeap相比,這是非常有限的。擁有太多的字符串文字會很快占滿這個空間,從而導java.lang.OutOfMemoryError。值得慶幸的是,Java語言設計者已經意識到了這個問題,從Java 7開始,他們將字符串池移動到正常的堆空間,這比PermGen空間要大得多。使字符串成為不可變的還有另一個缺點,因為它限制了它的可擴展性。現在,您不能擴展String來提供更多的功能,雖然很少需要。

java裏String類為何被設計為final