ぺんぎんらぼ

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です。たまに絵を描きます。

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です

なぜequalsとhashCodeの両方をオーバーライドする必要があるのか

Javaで、データを格納するためのBeanクラスを作成したときに、ほとんどの場合、getter、setterも併せて実装します。
それとは別に、コンストラクタやequalsメソッド、hashCodeメソッドも実装するかもしれません。
今回は、このequalsメソッド、hashCodeメソッドのお話です。

はじめに、皆さん、このequalsメソッドとhashCodeメソッドの処理内容ってご存じでしょうか?

equalsメソッド

equalsメソッドは値の同値性を検証するものです。

String型オブジェクトで、同一の文字列であることを検証するときにequalsメソッドを使いますよね。

「==」演算子オブジェクトの同一性を検証するもので、「equals」メソッドは値の同値性を検証するものです。なので、Stringの文字列が同値であることを検証するのであれば、equalsメソッドを使用する必要があります。

        String s1 = new String("matsuki");
        String s2 = new String("matsuki");

        System.out.println(s1 == s2);      // 別オブジェクトなのでfalseになる
        System.out.println(s1.equals(s2)); // 同一の値なのでtrueになる

hashCodeメソッド

hashCodeメソッドはオブジェクトのハッシュ値を求めるものです。
ハッシュ値とは、オブジェクトの内容を基に算出された数値です。

ハッシュ値は次のような性質があります。

  • 元となる値が同一である場合、ハッシュ値少なからず同一の値になる
  • 元となる値が不一致である場合、ハッシュ値同じ値になるかもしれないし、別の値になるかもしれない

この特性から、断定的に言えることは、ハッシュ値が不一致の場合、元の値も不一致である、ということです。

hashCodeメソッドを直接使うことはあまりありませんが、Javaでは、2つのオブジェクトの同値性を検証するときに、hashCodeメソッドとequalsメソッドの2つが使われるケースがあります。

HashSetの「Hash」はハッシュ値の「Hash」

では、hashCodeメソッドとequalsメソッドが必要になるケースをJavaの標準ライブラリであるHashSetクラスを例に説明します。

HashSetクラスは、オブジェクトとともに、オブジェクトごとのハッシュ値を保持しています。

f:id:penguinlabo:20210511232457p:plain

オブジェクトの内容が同一の場合は、ハッシュ値必ず同一になりますが、見ての通り、オブジェクトの値が同一でない場合も、ハッシュ値同一になる場合があります

このHashSetに、新しいオブジェクトを追加するとします。HashSetは、値の重複は許さないので、初めに追加するオブジェクトと同じ値を持つオブジェクトが格納済みか検索します。

f:id:penguinlabo:20210511232931p:plain

検索方法としては、追加しようとしているオブジェクトのハッシュ値をhashCodeメソッドで取得します。
HashSetに格納されているオブジェクトは、ハッシュ値をすでに持っているので、追加するオブジェクトのハッシュ値とHashSetに格納されているハッシュ値を比較します。
ポイントは、hashCodeメソッドの呼び出しは、追加しようとしているオブジェクトで1回だけ呼び出されるということです。ハッシュ値の計算は1回だけです。

f:id:penguinlabo:20210511233246p:plain

ハッシュ値が一致しても値が一致するとは限らないので、ハッシュ値が一致したオブジェクトと追加しようとするオブジェクトの同値性をequalsメソッドで検証します。

なぜ、はじめにハッシュ値を比較するのか

この比較方法を見て、「ハッシュ値の比較なんてせず、はじめからequalsメソッドで比較すればいいのに」と思う人もいるかもしれません。

しかし、equalsメソッドはオブジェクトの中身のすべてが同一であることを検証するので、実行コストがかかる処理です。
それに対し、ハッシュ値の比較は、int値を比較するだけなので、極めて実行コストが低いのです。

実行コストの低い比較で、ある程度、ふるいにかけておいて、一致する可能性があるものを絞り込んだうえでequalsメソッドで同値性の検証をすることで、効率的に処理をしているのです。

試しに100万個のオブジェクトが格納されているHashSetに、新たなオブジェクトを追加するパフォーマンスを測定してみました。

hashCodeメソッドなし hashCodeメソッドあり
414ミリ秒 170ミリ秒

hashCodeメソッドがない場合は、ある場合と比較して2倍以上の処理時間がかかっています。
ただし、hashCodeがないからと言って、処理結果が変わるようなことはなく、処理自体は正しく行われます。

まとめ

equalsメソッドは、オブジェクトの同値性を検証するもの。オブジェクトの値が同一かどうかを検証するのであれば、equalsメソッドだけで実現できますが、比較にハッシュ値を利用する処理であれば、hashCodeメソッドを実装することでパフォーマンスの向上が見込めます。

値の同値性を求めるオブジェクトの場合は、equalsメソッドとhashCodeメソッドの両方を実装しておいたほうが良いです。
eclipseなどのIDEでコードの自動生成ができますし、lombokコンパイル時に動的に生成させることもできます。