Java的equals()和hashCode()方法存在于Object类中。因此,每个Java类都会获得equals()和hashCode()的默认实现。在本文中,我们将详细介绍Java的equals()和hashCode()方法。
Java equals()
Object类定义了以下equals()方法:
public boolean equals(Object obj) {
return (this == obj);
}
根据Java文档中equals()方法的说明,任何实现都应遵守以下原则。
- 对于任何对象x,
x.equals(x)
应返回true
。 - 对于任何两个对象x和y,只有当
y.equals(x)
返回true
时,x.equals(y)
才应返回true
。 - 对于多个对象x,y和z,如果
x.equals(y)
返回true
且y.equals(z)
返回true
,则x.equals(z)
应返回true
。 - 多次调用
x.equals(y)
应返回相同的结果,除非在equals()
方法实现中使用的任何对象属性被修改。 - Object类的equals()方法实现仅当两个引用指向同一对象时才返回
true
。
Java hashCode()
Java Object hashCode() 是一個本機方法,返回對象的整數哈希碼值。 hashCode() 方法的一般契約是:
- hashCode() 的多次調用應返回相同的整數值,除非修改了在 equals() 方法中使用的對象屬性。
- 對象的哈希碼值在同一應用程序的多次執行中可以更改。
- 如果兩個對象根據 equals() 方法相等,則它們的哈希碼必須相同。
- 如果兩個對象根據 equals() 方法不相等,則它們的哈希碼不需要不同。它們的哈希碼值可能相等,也可能不相等。
equals() 和 hashCode() 方法的重要性
Java 中的 hashCode() 和 equals() 方法用於基於哈希表的實現中,用於存儲和檢索數據。我已在 Java 中的 HashMap 工作原理?中詳細解釋了它。equals() 和 hashCode() 的實現應遵循這些規則。
- 如果
o1.equals(o2)
,則o1.hashCode() == o2.hashCode()
應始終為true
。 - 如果
o1.hashCode() == o2.hashCode
為真,這並不意味著o1.equals(o2)
將為true
。
何時覆蓋 equals() 和 hashCode() 方法?
當我們覆蓋 equals() 方法時,幾乎必須同時覆蓋 hashCode() 方法,以確保我們的實現不違反它們的契約。請注意,如果 equals() 和 hashCode() 契約被違反,您的程序將不會拋出任何異常,如果您不打算將該類用作哈希表鍵,那麼這不會造成任何問題。如果您計劃將類用作哈希表鍵,那麼覆蓋 equals() 和 hashCode() 方法是必需的。讓我們看看當我們依賴於 equals() 和 hashCode() 方法的默認實現並使用自定義類作為 HashMap 鍵時會發生什麼情況。
package com.journaldev.java;
public class DataKey {
private String name;
private int id;
// getter 和 setter 方法
@Override
public String toString() {
return "DataKey [name=" + name + ", id=" + id + "]";
}
}
package com.journaldev.java;
import java.util.HashMap;
import java.util.Map;
public class HashingTest {
public static void main(String[] args) {
Map<DataKey, Integer> hm = getAllData();
DataKey dk = new DataKey();
dk.setId(1);
dk.setName("Pankaj");
System.out.println(dk.hashCode());
Integer value = hm.get(dk);
System.out.println(value);
}
private static Map<DataKey, Integer> getAllData() {
Map<DataKey, Integer> hm = new HashMap<>();
DataKey dk = new DataKey();
dk.setId(1);
dk.setName("Pankaj");
System.out.println(dk.hashCode());
hm.put(dk, 10);
return hm;
}
}
當我們運行上述程序時,它將打印 null
。這是因為 Object 的 hashCode() 方法用於查找存儲桶以查找鍵。由於我們無法訪問 HashMap 的鍵,並且我們正在重新創建鍵來檢索數據,您將注意到兩個對象的哈希碼值不同,因此找不到值。
實現 equals() 和 hashCode() 方法
我們可以定義自己的equals()和hashCode()方法實現,但如果我們實現不慎,運行時可能會出現奇怪的問題。幸運的是,現在大多數的IDE都提供了自動實現它們的方式,如果需要,我們還可以根據需求進行更改。我們可以使用Eclipse自動生成equals()和hashCode()方法。 這是自動生成的equals()和hashCode()方法實現。
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DataKey other = (DataKey) obj;
if (id != other.id)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
請注意,equals()和hashCode()方法都使用相同的字段進行計算,以保持它們的契約有效。如果您再次運行測試程序,將從映射中獲取對象,並且程序將打印10。我們還可以使用 Project Lombok 來自動生成equals和hashCode方法實現。
什麼是哈希碰撞
簡而言之,Java哈希表實現使用以下邏輯進行get和put操作。
- 首先,使用“key”哈希碼識別要使用的“桶”。
- 如果該桶中沒有具有相同哈希碼的對象,則將對象添加到put操作中,並對get操作返回null。
- 如果在桶中有其他具有相同哈希碼的對象,則“key”等方法將發揮作用。
- 如果 equals() 返回 true 並且這是一個 put 操作,則對象值將被覆蓋。
- 如果 equals() 返回 false 並且這是一個 put 操作,則新條目將被添加到桶中。
- 如果 equals() 返回 true 並且這是一個 get 操作,則將返回對象值。
- 如果 equals() 返回 false 並且這是一個 get 操作,則將返回 null。
下圖顯示了 HashMap 的桶項目以及它們的 equals() 和 hashCode() 之間的關係。 當兩個鍵具有相同的哈希碼時出現的現象稱為哈希碰撞。如果 hashCode() 方法未正確實現,將會有更多的哈希碰撞,並且映射條目將無法正確分佈,從而導致 get 和 put 操作的速度變慢。這就是在生成哈希碼時使用質數的原因,以便映射條目在所有桶中得到正確分佈。
如果我们不实现hashCode()和equals()会发生什么呢?
我们已经在上面看到,如果未实现hashCode(),我们将无法检索值,因为HashMap使用哈希码来找到桶并查找条目。如果只使用hashCode()而不实现equals(),同样也无法检索值,因为equals()方法将返回false。
实现equals()和hashCode()方法的最佳实践
- 在equals()和hashCode()方法的实现中使用相同的属性,以确保它们的契约在更新任何属性时不会违反。
- 最好使用不可变对象作为哈希表键,以便我们可以缓存哈希码,而不是在每次调用时计算它。这就是为什么String是哈希表键的良好选择,因为它是不可变的并且可以缓存哈希码值。
- 实现hashCode()方法,以便发生最少的哈希冲突,并确保条目均匀分布在所有桶中。
你可以從我們的GitHub存儲庫下載完整的代碼。
Source:
https://www.digitalocean.com/community/tutorials/java-equals-hashcode