Equals Method Overloading

import java.util.*;

class Student {
    private int id;

    Student(int id) {
        this.id = id;
    }

    public boolean equals(Student student) {
        return student != null && student.id == id;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

public class Main {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student(1));
        students.add(new Student(2));

        students.remove(new Student(1));

        System.out.println(students.size());
  }
}

上のコードを実行した時の結果はどれになるでしょうか?

  1. 1
  2. 2
  3. コンパイルエラー
  4. 実行時エラー

答え

選択肢2の2が出力されます。


解説

Studentを受け取るequalsを作れば比較する時クラスの比較すれば良くない?みたいな話を聞いたのでそれを問題にしてみました。

なぜ2が呼ばれているかというとremove内で比較するときにequals(Object)のほうが呼ばれているからです。
以下のコードを考えてみます。

Student student1 = new Student(1);
Object student2 = new Student(1);
student1.equals(student2); //false

上記のコードを実行したとき、変数の型でどのメソッドを呼び出すか決めているのでObject型のequalsが呼び出されます。

では、次に問題のコードをデコンパイルしてみます。

public static void main(String[] args) {
    ArrayList students = new ArrayList();
    students.add(new Student(1));
    students.add(new Student(2));
    students.remove(new Student(1));
    System.out.println(students.size());
}

コンパイル後だと型の情報が消えてraw型になっています。
あとは内部でequalsが使用されたときObject#equals(Object)されていることがわかると思います。


解決策

今にオーバーロードしてもObject#equals(Object)が使用されるケースがあるので、必ずObject#equals(Object)をオーバーライドするようにしてください。

class Student {
    private int id;

    Student(int id) {
        this.id = id;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return id == student.id;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}