【Java】equalsメソッドのオーバーライドについて:必要性と実装方法

アイキャッチ画像

記事の対象者

  • Javaを学習中あるいは実務で使っているエンジニア(初級者~中級者)
  • 「==」ではなくequalsメソッドで文字列比較すべきことを理解している人

equalsメソッドとは

Javaのequalsメソッドは、Objectクラスに定義されているメソッドです。
よく使われるシーンは「文字列(String型)の比較」で、値が同じかどうかの判定に利用されます。

よくある間違いとして、文字列の比較に論理演算子「==」を使うケースがあります。
「==」は値の比較ではなく参照の比較になるため、値が同じでも結果がfalseになることがあります。

オーバーライドしなかったら

自分で定義したクラスのインスタンス同士を同値比較したいときは少し注意が必要です。
具体例で見てみましょう。

以下のようなクラスとメソッドを作りたいと思います。

Nameクラス

  • フィールド:
    firstName:String
    lastName:String
  • equalsメソッド
    2つのNameインスタンスを比較し、同姓同名(firstNameが同値かつlastNameが同値)ならtrue、それ以外はfalseを返す。

すべてのクラスはObjectクラスのサブクラス(=継承している)なので、Objectクラスが元々持っているequalsメソッドを使うことができます。

Nameクラスを定義し、苗字と名前が同じインスタンスを2つ作り、
equalsメソッドをオーバーライドしないで比較してみましょう。

class Name {
    String firstName;
    String lastName;
    // コンストラクタ

    Name(String firstName, String lastName){
        this.firstName = firstName;
        this.lastName = lastName;
    }
}
public class Main {
    public static void main(String[] args) {
        Name name1 = new Name("山田", "太郎");
        Name name2 = new Name("山田", "太郎");
        System.out.println(name1.equals(name2));
    }
}

// 実行結果

// false

さて、name1とname2それぞれが持つフィールドは全く同じ値ですが、equalsメソッドの戻り値はfalseです。同値のはずなのに変ですね。

これは一見おかしく見えますが、バグではありません
理由は、equalsメソッドが参照の比較をしているからです。

え?Stringだと同値はtrueだったのに?と思うのも当然です。
実は、StringやListなどのequalsメソッドは同値比較できるように例外的にオーバーライドされているのです。

(参考)String.javaで定義(オーバーライド)されているequalsメソッド

そのため、これら以外のクラスで値の比較をしたい場合、自分でequalsメソッドをオーバーライドする必要があります。

オーバーライドが必要な理由

じゃあ、equalsメソッドではなく独自に値を比較するメソッドを作ればよいだけでは?と思ったかもしれません。しかし、そのやり方は推奨されません。

なぜなら、以下のようなJavaの標準APIでは、equalsメソッドによる同値判定が前提となっているからです。

  • HashSetHashMap の重複チェック
  • List.contains() での要素の存在判定

独自メソッドで比較しても、これらのAPIに影響を与えることはできず、思わぬ不具合につながります。
equalsメソッドを正しくオーバーライドすれば、こうしたAPIも自動的に期待通り動作します。

equalsメソッドのオーバーライド方法

オーバーライドが必要なのはわかりましたが、コードはどう書けばよいのでしょうか。

また、equalsメソッドはhashCodeメソッドと関連づいているため、equalsメソッドを自分でオーバーライドした場合、hashCodeメソッドもオーバーライドする必要があります。これを自力でやるのは大変です。

EclipseやVisual Studio Codeでは、equalsメソッドとhashCodeメソッドのオーバーライドを自動で実装してくれる機能がついています。

今回はVisual Studio Codeでの方法を紹介します。

※拡張機能のExtension: Java Extension Packなどで環境が整っていること前提。

①equalsとhashCodeのオーバーライドを実装したいクラスのどこかにカーソルを合わせ、右クリックから「ソースアクション」を選択します。

②「equals()およびhashCode()の生成」をクリックします。

③equals()およびhashCode()に含めるフィールドの選択、とでるので

firstNameとlastName両方にチェックを入れ、OKを押します。

hashCodeとequalsメソッドが自動で生成されました!

これで動作確認をしてみましょう!
・生成されたhashCodeメソッドとequalsメソッド

@Override
public int hashCode() {
        int hash = 7;
        hash = 79 * hash + Objects.hashCode(this.firstName);
        hash = 79 * hash + Objects.hashCode(this.lastName);
        return hash;

    }

@Override
public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Name other = (Name) obj;
        if (!Objects.equals(this.firstName, other.firstName)) {
            return false;
        }
        return Objects.equals(this.lastName, other.lastName);
    }

・確認用コード

public class Main {
    public static void main(String[] args) {
        Name name1 = new Name(“山田”, “太郎”);
        Name name2 = new Name(“山田”, “太郎”);
        Name name3 = new Name(“山田”, “次郎”);
        Name name4 = new Name(“田中”, “太郎”);
        Name name5 = new Name(“田中”, “次郎”);
        System.out.println(name1.equals(name2)); // 苗字・名前ともに同じ
        System.out.println(name1.equals(name3)); // 苗字だけ同じ
        System.out.println(name1.equals(name4)); // 名前だけ同じ
        System.out.println(name1.equals(name5)); // 苗字・名前ともに違う
    }
}
// 実行結果
// true
// false
// false
// false
// false

「苗字・名前ともに同じ」の場合だけtrue、それ以外はfalseという期待通りの処理ができました!

まとめ

今回はJavaでequalsメソッドをオーバーライドする意味と使い方について紹介しました。

ポイント

  • equalsをオーバーライドしないと参照比較になる
  • StringList特別にオーバーライドされている
  • 値で比較したいなら、equalshashCode両方をオーバーライド
  • 自動生成機能(VSCodeやEclipse)を積極的に活用しよう