簡體   English   中英

為什么String類是不可變的,即使它有一個名為“hash”的非最終字段

[英]Why String class is immutable even though it has a non -final field called “hash”

我正在閱讀Joshua Bloch撰寫的有效Java第15項。 在第15項中談到“最小化可變性”時,他提到了使對象不可變的五條規則。 其中之一就是讓所有領域都是最終的。 這是規則:

使所有字段成為最終字段 :這清楚地以系統強制執行的方式表達您的意圖。 此外,如果對新創建的實例的引用在沒有同步的情況下從一個線程傳遞到另一個線程,則必須確保正確的行為,如內存模型中所述[JLS,17.5; Goetz06 16]。

我知道String類是一個不可變類的例子。 通過源代碼我看到它實際上有一個非最終的哈希實例。

//Cache the hash code for the string
private int hash; // Default to 0

String如何成為不可變的呢?

該評論解釋了為什么這不是最終的:

//緩存字符串的哈希碼

這是一個緩存。 如果不調用hashCode ,則不會設置它的值。 它可以在創建字符串期間設置,但這意味着更長的創建時間,對於您可能不需要的功能(哈希代碼)。 另一方面,每次詢問時計算哈希值都是浪費,給字符串是不可變的 ,哈希碼永遠不會改變。

有一個非final字段的事實確實與你引用的定義有些矛盾,但這里它不是對象接口的一部分。 它只是一個內部實現細節,它對字符串的可變性沒有影響(作為字符容器)。

編輯 - 由於受歡迎的需求,完成我的答案:雖然hash不是公共接口的直接部分,但它可能會影響該接口的行為,因為hashCode返回其值。 現在,由於hashCode未同步,如果多個線程同時使用該方法,則可能會多次設置hash 但是,設置為hash值始終是穩定計算的結果,該計算僅依賴於最終字段( valueoffsetcount )。 因此,散列的每次計算都會產生完全相同的結果。 對於外部用戶,這就像hash計算一次 - 就像每次都計算hash一樣,因為hashCode的契約要求它始終為給定值返回相同的結果。 最重要的是,即使hash不是最終的,它的可變性永遠不會被外部查看者看到,因此該類可以被認為是不可變的。

String是不可變的,因為就其用戶而言,它永遠不會被修改,並且對所有線程看起來總是相同。

hashCode()是使用racy single-check成語(EJ item 71)計算的,它是安全的,因為如果hashCode()被意外地計算多次,它不會傷害任何人。

使所有字段成為最終是使類不可變的最簡單和最簡單的方法,但並不是嚴格要求的。 只要所有方法返回相同的東西,無論哪個線程調用它,該類都是不可變的。

即使String是不可變的,它也可以通過反射改變。 如果你做哈希最終,如果發生這種情況,你可以把事情弄得一團糟。 哈希字段也是不同的,因為它主要是作為緩存,一種加速hashCode()的計算的方法,並且應該被認為是計算字段,而不是常量。

, and for instances of the class to be able to switch among them. 在許多情況下,對於邏輯上不可變的類,對於相同的具有若干不同的表示,並且類的實例能夠在它們之間切換,這可能是有幫助的。 將從散列字段為零的字符串返回的散列碼值將與散列字段保存早期散列碼調用的結果時返回的值相同。 因此,將哈希值從前者更改為后者不會更改對象的可觀察狀態,但會導致將來的操作運行得更快。

以這些方式編碼事物的最大困難是

  1. 如果某個對象從持有對某個特定不可變對象的引用更改為持有對具有相同語義內容的其他對象的引用,則此類更改不應影響持有該引用的對象的可觀察狀態,但如果結果是假設相同的對象並不完全相同,可能會發生不好的事情,特別是如果假定持有引用的對象被認為可替代其他語義相同的對象。
  2. 即使沒有任何對象“相同”的錯誤,仍然存在這樣的危險,即與替換的線程看起來相同的對象可能看起來與其他線程不同。 這種情況不太可能發生,但如果確實發生,效果可能非常糟糕。

盡管如此,對不可變對象進行替換仍然有一些優點。 例如,如果程序將比較許多持有長字符串的對象,並且其中許多對象雖然是單獨生成的,但它們彼此相同,使用WeakDictionary構建不同字符串實例池並替換它們可能很有用。發現與池中的一個字符串相同的任何字符串,其中包含對池副本的引用。 這樣做會導致許多相同的字符串被映射到相同的字符串,從而極大地促進了它們之間可能進行的任何未來比較。 當然,正如所指出的那樣,對象在邏輯上是不可變的非常重要,比較是正確完成的。 在這方面的任何問題都可以將優化變成混亂。

創建一個不可變的對象你需要使類最終及其所有成員都是final,這樣一旦對象被創建,沒有人可以修改它的狀態。 您可以通過將成員設為非final而不是私有來實現相同的功能 ,除了在構造函數中之外不修改它們。

編輯:

注意:在散列字符串時,Java還會在散列屬性中緩存散列值,但前提是結果不等於零。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM