ビジネスコードを書くとき、私たちはしばしば 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 つの要素になります。