首頁 > 科技週邊 > IT業界 > 如何正確實現java s HashCode

如何正確實現java s HashCode

尊渡假赌尊渡假赌尊渡假赌
發布: 2025-02-18 10:46:14
原創
664 人瀏覽過

SitePoint 探索 Java 世界:誠邀 Java 開發者投稿

How to Implement Java's hashCode Correctly

SitePoint 持續拓展內容領域,近期將重點關注 Java。如果您是經驗豐富的 Java 開發者,並希望為我們的 Java 內容貢獻力量,歡迎聯繫我們,分享您想撰寫的文章主題構想。

Java 中 equalshashCode 方法的正確實現

您已為您的類實現了 equals 方法?很棒!但您也必須實現 hashCode 方法。讓我們了解原因以及如何正確實現它。

關鍵要點:

  • 在 Java 中,相等的對象應具有相同的哈希碼。因此,如果重寫了 equals 方法,則必須創建匹配的 hashCode 實現,以確保在基於哈希的集合中存儲和檢索對象的準確性和一致性。
  • 實現 hashCode 時,應使用與 equals 方法中使用的相同字段。應盡量避免使用可變字段和集合,因為這可能會導致性能問題。
  • 哈希碼與性能優化相關,因此除非性能分析表明需要改進,否則不應在哈希上投入過多精力。
  • 哈希衝突(兩個不同的對象具有相同的哈希碼)可以通過改進哈希算法和使用更大的質數作為乘數來減少。這有助於更均勻地將哈希碼分佈在集合中,從而減少哈希衝突的可能性並確保更快的數據檢索。

equalshashCode 方法

雖然 equals 方法從一般角度來看是合理的,但 hashCode 方法則更具技術性。嚴格來說,它只是一個用於提高性能的實現細節。

大多數數據結構使用 equals 方法來檢查它們是否包含某個元素。例如:

List<String> list = Arrays.asList("a", "b", "c");
boolean contains = list.contains("b");
登入後複製
登入後複製
登入後複製
登入後複製

變量 contains 為真,因為雖然 "b" 的實例並不相同(再次忽略字符串駐留),但它們是相等的。

然而,將每個元素與傳遞給 contains 方法的實例進行比較效率低下,而一類數據結構則使用更高效的方法。它們不將請求的實例與它們包含的每個元素進行比較,而是使用快捷方式來減少可能相等的實例數量,然後只比較這些實例。

這個快捷方式就是哈希碼,它可以被視為對象的相等性簡化為一個整數值。具有相同哈希碼的實例不一定是相等的,但相等的實例具有相同的哈希碼。 (或者應該具有相同的哈希碼,我們稍後將討論這一點。)此類數據結構通常以其技術名稱命名,其名稱中包含 "Hash",其中 HashMap 是最著名的代表。

它們通常的工作方式如下:

  • 添加元素時,使用其哈希碼計算內部數組(稱為桶)中的索引。
  • 如果其他不相等的元素具有相同的哈希碼,則它們最終會進入同一個桶中,並且必須捆綁在一起,例如通過將它們添加到列表中。
  • 將實例傳遞給 contains 方法時,使用其哈希碼計算桶。只有其中的元素才會與該實例進行比較。

這樣,實現 contains 方法可能只需要很少的,理想情況下不需要任何 equals 比較。

equals 方法一樣,hashCode 方法也在 Object 類中定義。

關於哈希的思考

如果 hashCode 方法用作確定相等性的快捷方式,那麼我們真正應該關心的只有一件事:相等的對象應該具有相同的哈希碼。

這也是為什麼如果我們重寫 equals 方法,就必須創建一個匹配的 hashCode 實現的原因!否則,根據我們的實現相等的事物可能不會具有相同的哈希碼,因為它們使用 Object 類的實現。

hashCode 方法的約定

引用源代碼:

hashCode 方法的一般約定是:

  • 每當在 Java 應用程序的執行過程中多次對同一個對象調用它時,hashCode 方法必須始終返回相同的整數,前提是沒有修改在對象的 equals 比較中使用的信息。此整數不必在一個應用程序的執行與同一應用程序的另一個執行之間保持一致。
  • 如果根據 equals(Object) 方法,兩個對象相等,則對這兩個對像中的每一個調用 hashCode 方法必須產生相同的整數結果。
  • 如果根據 equals(Object) 方法,兩個對像不相等,則不需要調用這兩個對像上的 hashCode 方法必須產生不同的整數結果。但是,程序員應該意識到,為不相等的對像生成不同的整數結果可以提高哈希表的性能。

第一點反映了 equals 方法的一致性屬性,第二點是我們上面得出的要求。第三點說明了一個重要的細節,我們稍後將討論。

實現 hashCode 方法

一個非常簡單的 Person.hashCode 實現如下:

List<String> list = Arrays.asList("a", "b", "c");
boolean contains = list.contains("b");
登入後複製
登入後複製
登入後複製
登入後複製

人的哈希碼是通過計算相關字段的哈希碼並將它們組合起來計算的。兩者都留給 Objects 的實用程序函數 hash 來處理。

選擇字段

但是哪些字段是相關的呢?這些要求有助於回答這個問題:如果相等的對象必須具有相同的哈希碼,那麼哈希碼計算不應包含任何未用於相等性檢查的字段。 (否則,只有在這些字段上不同的兩個對象將是相等的,但具有不同的哈希碼。)

因此,用於哈希的字段集應該是用於相等性的字段集的子集。默認情況下,兩者都將使用相同的字段,但有一些細節需要考慮。

一致性

首先,有一致性要求。它應該被相當嚴格地解釋。雖然它允許在某些字段發生更改時哈希碼發生更改(對於可變類來說這通常是不可避免的),但哈希數據結構並未為此場景做好準備。

正如我們上面所看到的,哈希碼用於確定元素的桶。但是,如果哈希相關字段發生更改,則不會重新計算哈希,並且不會更新內部數組。

這意味著使用相等的對像或甚至使用完全相同的實例的後續查詢將失敗!數據結構計算當前的哈希碼(與用於存儲實例的哈希碼不同),並在錯誤的桶中查找。

結論:最好不要使用可變字段進行哈希碼計算!

性能

哈希碼的計算次數可能與 equals 方法的調用次數大致相同。這很可能發生在代碼的關鍵性能部分,因此考慮性能是有意義的。並且與 equals 方法不同,這裡有更多空間進行優化。

除非使用複雜的算法或涉及許多字段,否則組合其哈希碼的算術成本與不可避免的成本一樣微不足道。但是應該考慮是否需要將所有字段都包含在計算中!特別是應該對集合持懷疑態度。例如,列表和集合將為它們的每個元素計算哈希值。是否需要調用它們應該根據具體情況進行考慮。

如果性能至關重要,使用 Objects.hash 也可能不是最佳選擇,因為它需要為其可變參數創建數組。

但是關於優化的通用規則仍然適用:不要過早優化!使用常見的哈希碼算法,也許放棄包含集合,並且只有在性能分析顯示存在改進的可能性後才進行優化。

衝突

全力以赴追求性能,那麼這個實現怎麼樣?

List<String> list = Arrays.asList("a", "b", "c");
boolean contains = list.contains("b");
登入後複製
登入後複製
登入後複製
登入後複製

它肯定很快。並且相等的對象將具有相同的哈希碼,所以我們在這方面也很好。作為獎勵,沒有涉及可變字段!

但是請記住我們之前關於桶的內容?這樣所有實例都將進入同一個桶!這通常會導致一個鍊錶保存所有元素,這對性能來說非常糟糕。例如,每個 contains 調用都會觸發鍊錶的線性掃描。

因此,我們希望盡可能減少同一個桶中的項目數量!即使對於非常相似的對象,也能返回差異很大的哈希碼的算法是一個良好的開端。如何實現部分取決於所選字段。我們在計算中包含的細節越多,哈希碼不同的可能性就越大。請注意,這與我們對性能的想法完全相反。因此,有趣的是,使用過多過少的字段都可能導致性能不佳。

防止衝突的另一部分是用於實際計算哈希的算法。

計算哈希值

計算字段哈希碼的最簡單方法是對其調用 hashCode 方法。可以手動組合它們。一個常見的算法是從某個任意數字開始,然後重複地將其與另一個數字(通常是一個小的質數)相乘,然後再添加字段的哈希值:

List<String> list = Arrays.asList("a", "b", "c");
boolean contains = list.contains("b");
登入後複製
登入後複製
登入後複製
登入後複製

這可能會導致溢出,但這在 Java 中不會導致異常,因此問題不大。

請注意,即使是優秀的哈希算法,如果輸入數據具有特定模式,也可能導致異常頻繁的衝突。作為一個簡單的例子,假設我們通過添加點的 x 和 y 坐標來計算點的哈希值。這聽起來還不錯,直到我們意識到我們經常處理直線 f(x) = -x 上的點,這意味著對於所有這些點,x y == 0。衝突,很多!

但是再次強調:使用常見的算法,並且除非性能分析顯示存在問題,否則不要擔心。

總結

我們已經看到,計算哈希碼就像將相等性壓縮為整數值:相等的對象必須具有相同的哈希碼,並且出於性能原因,最好盡可能少的不相等對象共享相同的哈希碼。

這意味著如果重寫了 equals 方法,則必須始終重寫 hashCode 方法。

實現 hashCode 方法時:

  • 使用與 equals 方法中使用的相同字段(或其子集)。
  • 最好不要包含可變字段。
  • 考慮不調用集合上的 hashCode 方法。
  • 使用常見的算法,除非輸入數據的模式與之相反。

記住,hashCode 方法與性能有關,因此除非性能分析表明有必要,否則不要浪費太多精力。

關於正確實現 Java hashCode 方法的常見問題解答 (FAQ)

Java 中 hashCode() 方法的意義是什麼?

Java 中的 hashCode() 方法是一個內置函數,它返回一個整數值。它主要用於基於哈希的集合(如 HashMapHashSetHashTable)以更有效地存儲和檢索對象。 hashCode() 方法與 equals() 方法協同工作,以確保每個對像都有一個唯一的標識符。這有助於快速檢索數據,尤其是在大型集合中,從而提高 Java 應用程序的性能。

Java 中 hashCode() 方法是如何工作的?

Java 中的 hashCode() 方法的工作原理是生成一個整數值,該值表示對象的內存地址。此值用作對像在基於哈希的集合中的索引號。當您對對象調用 hashCode() 方法時,它會使用哈希算法來生成此唯一整數。但是,需要注意的是,兩個不同的對象可能具有相同的 hashCode,這被稱為哈希衝突。

Java 中 equals()hashCode() 方法之間的約定是什麼?

Java 中 equals()hashCode() 方法之間的約定是一組規則,用於管理它們的交互。該約定指出,如果根據 equals() 方法,兩個對象相等,則對這兩個對像中的每一個調用 hashCode() 方法必須產生相同的整數結果。這確保了在基於哈希的集合中存儲和檢索對象時的一致性和準確性。

如何在 Java 中重寫 hashCode() 方法?

在 Java 中重寫 hashCode() 方法包括提供您自己的實現,該實現為每個對象返回一個唯一的整數。這可以通過使用對象的實例變量和質數乘數來實現。質數有助於將哈希碼均勻地分佈在集合中,從而減少哈希衝突的可能性。

什麼是哈希衝突,如何避免?

哈希衝突是指 hashCode() 方法為兩個不同的對像生成相同的整數。如果處理不當,這可能會導致數據丟失。為了避免哈希衝突,您可以改進哈希算法以生成更多唯一的整數。此外,使用更大的質數作為乘數可以幫助更均勻地將哈希碼分佈在集合中。

為什麼應該重寫 hashCode() 方法?

重寫 hashCode() 方法可以提高 Java 應用程序的性能,尤其是在處理大型集合時。通過提供您自己的實現,您可以生成更唯一且更均勻分佈的哈希碼,從而減少哈希衝突的可能性並確保更快的數據檢索。

在 Java 中,兩個不相等的對象可以具有相同的 hashCode 嗎?

是的,在 Java 中,兩個不相等的對象可以具有相同的 hashCode。這被稱為哈希衝突。但是,通過改進哈希算法和使用更大的質數作為乘數,可以降低這種情況發生的可能性。

如果我不重寫 hashCode() 方法會發生什麼?

如果您不重寫 hashCode() 方法,Java 將使用其默認實現,這可能無法為每個對象提供唯一的哈希碼。這可能導致哈希衝突和基於哈希的集合中更慢的數據檢索。

hashCode() 方法如何提高 Java 應用程序的性能?

hashCode() 方法通過為每個對象提供唯一的標識符來提高 Java 應用程序的性能。這允許在基於哈希的集合中更快地檢索數據,因為可以使用對象的哈希碼直接找到該對象,而無需搜索整個集合。

我可以在非基於哈希的集合中使用 hashCode() 方法嗎?

雖然 hashCode() 方法主要用於基於哈希的集合,但它也可以用於非基於哈希的集合。但是,其好處可能不那麼明顯,因為非基於哈希的集合不依賴於哈希碼進行數據存儲和檢索。

以上是如何正確實現java s HashCode的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板