Javaのequals()とhashCode()は同じです。
Javaのequals()メソッドとhashCode()メソッドは、Objectクラスに存在します。そのため、すべてのJavaクラスは、equals()およびhashCode()のデフォルトの実装を取得します。この投稿では、Javaのequals()メソッドとhashCode()メソッドについて詳しく見ていきます。
Java の equals() メソッドは〜と同じです。
以下の通り、equals()メソッドが定義されたオブジェクトクラスがあります。
public boolean equals(Object obj) {
return (this == obj);
}
Javaのequals()メソッドのドキュメントによれば、実装は以下の原則に従う必要があります。
- For any object x, x.equals(x) should return true.
- For any two object x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
- For multiple objects x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
- Multiple invocations of x.equals(y) should return same result, unless any of the object properties is modified that is being used in the equals() method implementation.
- Object class equals() method implementation returns true only when both the references are pointing to same object.
JavaのhashCode()メソッドを日本語で言い換えると: “ジャヴァのハッシュコード()”
JavaのObjectクラスのhashCode()メソッドは、オブジェクトの整数ハッシュコード値を返すネイティブメソッドです。hashCode()メソッドの一般契約は次の通りです。
- Multiple invocations of hashCode() should return the same integer value, unless the object property is modified that is being used in the equals() method.
- An object hash code value can change in multiple executions of the same application.
- If two objects are equal according to equals() method, then their hash code must be same.
- If two objects are unequal according to equals() method, their hash code are not required to be different. Their hash code value may or may-not be equal.
equals()メソッドとhashCode()メソッドの重要性
JavaのhashCode()メソッドとequals()メソッドは、データの格納と取得において、Javaのハッシュテーブルベースの実装で使用されます。この詳細は、JavaにおけるHashMapの動作について詳しく説明しています。equals()とhashCode()の実装は、以下のルールに従う必要があります。
- If o1.equals(o2), then o1.hashCode() == o2.hashCode() should always be true.
- If o1.hashCode() == o2.hashCode is true, it doesn’t mean that o1.equals(o2) will be true.
equals()メソッドとhashCode()メソッドをオーバーライドするタイミングはいつですか?
equals() メソッドをオーバーライドする場合、実装によって契約が破られないように hashCode() メソッドもオーバーライドする必要があります。なお、equals() と hashCode() の契約が破られた場合、プログラムは例外をスローしませんが、クラスをハッシュテーブルのキーとして使用する予定がない場合は問題ありません。しかし、クラスをハッシュテーブルのキーとして使用する場合は、equals() と hashCode() の両方をオーバーライドする必要があります。デフォルトの equals() と hashCode() メソッドの実装に頼って、カスタムクラスを HashMap のキーとして使用した場合に何が起こるか、見てみましょう。
package com.scdev.java;
public class DataKey {
private String name;
private int id;
// getter and setter methods
@Override
public String toString() {
return "DataKey [name=" + name + ", id=" + id + "]";
}
}
package com.scdev.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と表示されます。これは、オブジェクトのhashCode()メソッドを使用してバケツを見つけるためです。HashMapのキーにはアクセスできないため、データを取得するためにキーを再作成していますが、オブジェクトのハッシュコードの値が異なるため、値が見つかりません。
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メソッドの実装を自動生成することもできます。
ハッシュ衝突とは何ですか? (What is a hash collision?)
非常に単純に言えば、Javaのハッシュテーブルの実装では、get操作とput操作に次のロジックが使用されます。
-
- 最初に、「key(ハッシュコード)」を使って使用する「バケット」を特定します。
-
- 同じハッシュコードを持つバケットにオブジェクトが存在しない場合、put操作のためにオブジェクトを追加し、get操作のためにはnullを返します。
-
- 同じハッシュコードを持つ他のオブジェクトがバケットに存在する場合、「key」のequals()メソッドが使われます。
equals()がtrueを返し、put操作である場合、オブジェクトの値が上書きされます。
equals()がfalseを返し、put操作である場合、新しいエントリがバケットに追加されます。
equals()がtrueを返し、get操作である場合、オブジェクトの値が返されます。
equals()がfalseを返し、get操作である場合、nullが返されます。
もしhashCode()とequals()の両方を実装しなかったら、どうなるでしょうか?
上記で既に述べたように、hashCode()が実装されていない場合、HashMapはハッシュコードを使用してエントリを探すバケットを見つけることができないため、値を取得することはできません。hashCode()のみを使用してequals()を実装しない場合でも、valueは取得できません。なぜなら、equals()メソッドはfalseを返すからです。
equals()メソッドとhashCode()メソッドを実装するためのベストプラクティス
- Use same properties in both equals() and hashCode() method implementations, so that their contract doesn’t violate when any properties is updated.
- It’s better to use immutable objects as Hash table key so that we can cache the hash code rather than calculating it on every call. That’s why String is a good candidate for Hash table key because it’s immutable and cache the hash code value.
- Implement hashCode() method so that least number of hash collision occurs and entries are evenly distributed across all the buckets.
当社のGitHubリポジトリから完全なコードをダウンロードすることができます。