ビジネスコードを書くとき、私たちはしばしば 2 つの Java オブジェクトが同じかどうかを判断する必要があります。オブジェクトが等しいかどうかを判断する一般的な方法は、
==
、equals、hashcode の 3 つのメソッドを使用することです。本記事では、これら 3 つの使い方を明らかにしようとしています。
関係演算子 ==
#
==
を使用して 2 つのオブジェクトが等しいかどうかを判断します。これは、これら 2 つのオブジェクトのアドレスが等しいかどうかを判断します。
サンプルコード:
出力結果:
jack と tom は同じ Person を new で作成していますが、新しく作成された Person のオブジェクトアドレスは異なります。したがって、jack は tom と等しくありません。印刷されたアドレスの結果から、jack と bob のオブジェクトアドレスは同じであることがわかります。したがって、彼らの比較結果は true です。
equals()
メソッド#
Java のすべてのクラスは Object という親クラスを持ち、各クラスはこのクラスのメソッドを継承します。これには
equals()
メソッドも含まれます。
equals () の本質#
親クラスのメソッドを継承した後、子クラスは親クラスの同名メソッドをオーバーライドできます。equals()
メソッドをオーバーライドしていない場合、equals
メソッドを使用して 2 つのオブジェクトが等しいかどうかを判断することは、上記で述べた ==
演算子を使用して判断するのと同じ結果になります。判断されるのは、これら 2 つのオブジェクトのアドレスが等しいかどうかです。
サンプルコード:
出力結果:
実際、equals () メソッドの底層ソースコードを見ると、実際には ==
を使用して 2 つのオブジェクトを判断しています。
equals () メソッドのオーバーライド#
時には、2 つのオブジェクトのアドレスが同じかどうかを比較するだけでなく、2 つのオブジェクトの内容が同じかどうかを判断する必要があります。この場合、equals メソッドをオーバーライドする必要があります。
以下のように equals
をオーバーライドできます。
このようにオブジェクトの判断が異なります。今回はオブジェクトの内容が同じかどうかを判断します。
出力結果:
jack オブジェクトインスタンスと tom オブジェクトインスタンスはオブジェクトアドレスが異なりますが、equals () メソッドをオーバーライドしたため、比較の重点がオブジェクトの内容に置かれたため、比較結果は true です。
他の equals () オーバーライドの書き方#
多くの他のパッケージも
equals()
をオーバーライドしています。
Apache Commons Lang フレームワーク#
equals と ==
の違い#
- 基本型に対して、
==
は 2 つの値が等しいかどうかを判断します。基本型には equals () メソッドはありません。 - 参照型に対して、
==
は 2 つの変数が同じオブジェクトを参照しているかどうかを判断しますが、equals () はオーバーライドされていない場合、==
と同じで、オーバーライドされた場合は参照しているオブジェクトの内容が同じかどうかを判断します。
hashCode()
メソッド#
hashCode も Object クラスで定義されたメソッドで、2 つのオブジェクトが等しいかどうかを比較することもできます。このメソッドの戻り値は、オブジェクトのハッシュ値を呼び出すもので、このハッシュ値の型は
int
です。
hashCode () の実装#
Object クラスのソースコードを見ると、hashCode () メソッドには具体的な実装がないことがわかります。これはネイティブメソッドで、C 言語で実装されています。このメソッドが返すハッシュ値は、オブジェクトのメモリアドレスを整数に変換することによって得られます。
なぜ hashCode () メソッドが必要なのか#
上記で述べた equals メソッドはオブジェクト間の等しさを判断できますが、なぜ hashCode メソッドが必要なのでしょうか?
ソースコードのコメントにはこの理由が記載されています:
- このメソッドをサポートすることで、ハッシュテーブルが恩恵を受けることができます。例えば、
java.util.HashMap
が提供するハッシュテーブルです。
私たちは HashSet や HashMap などのコレクションクラスが要素をコレクションに追加する際に、常に操作を行うことを知っています:現在追加しようとしているオブジェクトが現在のコレクションにすでに格納されているかどうかを判断します。このとき、オブジェクト間の比較が関与します。
HashSet と HashMap はどちらも hashCode () メソッドを使用してオブジェクトが格納される位置を計算します。したがって、これらのコレクションクラスにオブジェクトを追加する前に、格納されるキーの hashCode 値を求める必要があります。
以下は HashMap ソースコードにおけるキーの hashCode 値を求めるメソッドです。
なぜ hashCode メソッドを使用するのか、equals メソッドを使用しないのか?
理由は、equals メソッドの呼び出しはより時間がかかるからです。
以下は実験です。
出力結果:
出力結果から、hashCode メソッドの呼び出しはほとんど時間がかからないことがわかります。なぜなら、本質的には 2 つの int 型値を比較しているからです。したがって、hashCode メソッドを使用して 2 つのオブジェクトを比較するのは非常に速いです。
なぜ equals () メソッドが必要なのか#
hashCode メソッドがこれほど速いので、なぜ equals メソッドが必要なのでしょうか?それは、hashCode メソッドには限界があるからです。
限界:2 つのオブジェクトの hashCode 値が等しいからといって、2 つのオブジェクトが等しいとは限りません。
これは、hashCode 値がハッシュ関数によって計算されるためです。一般的な計算プロセスは、配列の長さに対してモジュロを取ることです。この配列はメモリ配列であったり、集合配列であったりします。長さが有限であるため、毎回計算されるハッシュ値が異なることを避けることは難しく、"ハッシュ衝突" が発生する可能性があります。悪化したハッシュアルゴリズムは衝突を引き起こしやすくなります。
ハッシュ衝突は、2 つのオブジェクトが等しいかどうかを比較する際に影響を与えます。つまり、異なる 2 つのオブジェクトのハッシュ値が同じである可能性もあります。
したがって、hashCode メソッドだけでは不十分で、equals メソッドを使用してさらに判断する必要があります。
HashSet が要素をコレクションに追加する過程では、これら 2 つのメソッドが同時に使用されます。
- 要素を追加する手順:
- オブジェクトの hashCode 値を計算して、オブジェクトが追加される位置を判断します。
- 他のすでに追加されたオブジェクトの hashCode 値と比較します。
- 同じ hashCode 値が存在しない場合、オブジェクトは現在のコレクションに存在しないことが証明されます。2 つの等しいオブジェクトの hashCode 値は必ず等しいです。
- 同じ hashCode 値が存在する場合、equals メソッドを使用してさらに判断します。
- オブジェクトが現在のコレクションに存在しない場合、オブジェクトをコレクションに格納します。
このプロセスは、最初に hashCode メソッドを使用して判断することで、すでにコレクションに存在する要素が equals メソッドを呼び出す回数を減らし、プログラムの実行速度を向上させます。
equals と hashCode の関係#
『Effective Java』には次のように書かれています:
equals をオーバーライドする際は、必ず hashCode をオーバーライドすること
equals メソッドをオーバーライドしたクラスでは、必ず hashCode メソッドもオーバーライドしなければなりません。
もし equals のみをオーバーライドし、hashCode をオーバーライドしなかった場合、このクラスのオブジェクトがハッシュベースのコレクション(HashMap、HashSet、Hashtable を含む)に要素として追加されると問題が発生します。
サンプルコード:
出力結果:
hashCode メソッドをオーバーライドしなかったため、オブジェクトを作成するたびに、Object クラスの hashCode メソッドが呼び出されます。このメソッドは異なるハッシュ値を生成します。
前述のように、HashSet はオブジェクトが現在のコレクションに存在するかどうかを判断する際にオブジェクトのハッシュ値を使用します。したがって、equals メソッドと hashCode の比較結果が異なるため、HashSet には私たちが重複していると考える 2 つのオブジェクトが存在することになります。
したがって、equals メソッドをオーバーライドする際には、必ず hashCode メソッドもオーバーライドする必要があります。
では、hashCode メソッドをどのようにオーバーライドすればよいのでしょうか?
- オーバーライド時に遵守すべき原則:
- オブジェクトの equals メソッドで比較に使用される情報が変更されていない場合、同じオブジェクトの hashCode メソッドを複数回呼び出すと、常に同じ値を返す必要があります。
- 2 つのオブジェクトが equals メソッドで比較されて等しい場合、これら 2 つのオブジェクトの hashCode メソッドは同じ結果を返す必要があります。
出力結果:
オーバーライド後、2 つのオブジェクトの hashCode 値は同じになり、set コレクションの要素数も私たちが期待した結果:1 つの要素になります。