Java で、equals メソッドと hashcode メソッドをオーバーライドするのはなぜですか?
出典:
Medium この記事では、equals()とhashcode() という密接に関連した 2 つのメソッドに焦点を当てます。これらがどのように相互作用するか、そしてそれらを正しくオーバーライドする方法を学びます。
なぜ、equals() メソッドをオーバーライドするのでしょうか?
Java では、 ==、
+=、
-+などの演算子の動作をオーバーロードできません。それらは所定のプロセスに従って動作します。たとえば、
==演算子の演算を考えてみましょう。
== 演算子はどのように機能するのでしょうか?
比較されている 2 つの参照がメモリ内の同じインスタンスを指しているかどうかをチェックします。
==演算子は、2 つの参照がメモリ内の同じインスタンスを表す場合にのみ true と評価されます。サンプルコードを見てみましょう。
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
}
プログラムで 2 つのPerson オブジェクトを異なる場所に作成し、それらを比較したいとします。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println( person1 == person2 ); --> will print false!
ビジネスの観点から見ると、この 2 つは同じに見えますよね? しかし、JVM の場合は同じではありません。どちらも
newキーワードを使用して作成されるため、これらのインスタンスは異なるメモリ セグメントに配置されます。したがって、
==演算子はfalseを返します。
しかし、 ==演算子をオーバーライドできない場合、これら 2 つのオブジェクトを同じように扱うように JVM に伝えるにはどうすればよいでしょうか?
ここで.equals()メソッドが活躍します。
equals()をオーバーライドして、一部のオブジェクトが特定のフィールドに対して同じ値を持つかどうかを確認して、それらが等しいとみなすことができます。比較するフィールドを選択できます。
2 つのPersonオブジェクトが同じであるのは、年齢と名前が同じである場合に限られるとすると、IDE は次のようなもの を生成して、
equals()を自動的に作成します。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
前の例に戻りましょう。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1 == person2 ); --> will print false!
System.out.println ( person1.equals(person2) ); --> will print true!
はい、
==演算子をオーバーロードしてオブジェクトを希望どおりに比較することはできませんが、Java では別の方法、
equals()メソッドを提供しており、これを希望どおりにオーバーライドできます。
クラスに.equals()のカスタム バージョン(オーバーライドとも呼ばれる) を提供しない場合、 Objectクラスの事前定義された.equals()と==演算子は同じように動作することに注意してください。 デフォルトの
quals()メソッドは、 Objectから継承され、比較される両方のインスタンスがメモリ内で同じかどうかをチェックします。
なぜ hashCode() メソッドをオーバーライドするのでしょうか?
HashSetや
HashMapなどの Java の一部のデータ構造は、それらの要素に適用されるハッシュ関数に基づいて要素を格納します。ハッシュ関数は
hashCode()です。
.equals()メソッドのオーバーライドを選択できる場合は、
hashCode()メソッドのオーバーライドも選択できるはずです。これには理由があります。結局のところ、
Objectから継承された
hashCode() のデフォルトの実装では、メモリ内のすべてのオブジェクトが一意であると見なされます。これらのハッシュ データ構造に戻りましょう。これらのデータ構造には規則があります。
HashSet には重複した値を含めることはできず、HashMap には重複したキーを含めることはできません。HashSet は、各HashSet値がHashMap内のキーとして格納されるような方法で
HashMap を使用して実装されます。
HashMap はどのように機能しますか?
HashMap は、複数のセグメントを持つネイティブ配列です。各セグメントにはリンクされたリスト (
linkedList ) があります。このリンクされたリストにはキーが保存されます。
HashMap は、hashCode()メソッドを使用して各キーの正しい linkedList を見つけ、その
linkedListのすべての要素を反復処理し、それらの各要素に
quals()メソッドを適用して、その要素がそこに含まれているかどうかを確認します。
重複したキーは許可されません。HashMapの中に何かを入れると、キーはこれらのリンクされたリストの 1 つに保存されます。このキーがどのリンク リストに格納されるかは、そのキーの
hashCode()メソッドの結果によって示されます。つまり、
key1.hashCode()の結果が 4 の場合、その
key1 はそこに存在するLinkedList の配列の 4 番目のセグメントに格納されます。デフォルトでは、
hashCode()メソッドはインスタンスごとに異なる結果を返します。
==のように動作し、メモリ内のすべてのインスタンスを別のオブジェクトとして扱うデフォルト
のquals()がある場合、問題はありません。覚えているかもしれませんが、前の例では、年齢と名前が同じ場合に
personインスタンスが等しいとみなされるようにしたいと述べました。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1.equals(person2) ); --> will print true!
次に、値のペアとして特定の文字列を持つキーとしてこれらのインスタンスを保存するマップを作成しましょう。
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
Personクラス では、
hashCodeメソッドはオーバーライドされていませんが、
equalsメソッドはオーバーライドされています。デフォルトの
hashCode は、person1.hashCode()と
person2.hashCode()のJava インスタンスごとに異なる結果を与えるため、異なる結果が得られる可能性が高くなります。
私たちのマップは、異なるリンクされたリスト内の異なる人物で終わる可能性があります。
これはHashMap のロジックに反します。
結局のところ、HashMap には複数の同一のキーを含めることはできません。重要なのは、 Objectクラスから継承された デフォルトの
hashCode() では十分ではないということです。
Personクラスの
equals()メソッドをオーバーライドした後でも。そのため、
equalsメソッドをオーバーライドした後に
hashCode()メソッドをオーバーライドする必要があります。これを修正しましょう。
hashCode()メソッドをオーバーライドして、 equals()と同じフィールド、つまり
ageと
nameを考慮するようにする必要があります。
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
@Override
public int hashCode() {
int prime = 31;
return prime*Objects.hash(name, age);
}
hashCode()メソッド では、単純な値を使用しました (他の値を使用できます)。ただし、問題の発生を少なくするために素数を使用することをお勧めします。これらのキーをもう一度
HashMapに保存してみましょう。
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
person1.hashCode()と
person2.hashCode() は同じになります。それらが 0 であるとしましょう
。HashMap はセグメント 0 に移動し、その中に
LinkedList がperson1 を値「1」のキーとして保存します。2 番目のケースでは、
HashMap が再びバケット 0 に移動して、キー
person2 を値「2」で保存すると、それと等しい別のキーがそこにすでに存在していることがわかります。こうすると、前のキーが上書きされます。そして、 key
person2だけがHashMapに存在します。 これは、複数の同一のキーを使用できないという
HashMapルールの仕組みを学習した方法です。ただし、等しくないインスタンスは同じハッシュコードを持つことができ、等しいインスタンスは同じハッシュコードを返さなければならないことに注意してください。
GO TO FULL VERSION