equals() と hashCode()
CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 42/87。
Javaでオブジェクトを比較する場合、== 演算子は、2つの参照が同じメモリ位置を指しているかどうかを確認します。オブジェクトの実際のプロパティを比較するには、equals() メソッドをオーバーライドする必要があります。
public class Person {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && name.equals(person.name);
}
}equals() メソッドは標準的なパターンに従います。まず同じオブジェクトであるかを確認し、次に型を検証し、最後に、関連するフィールドを比較します。
equals() をオーバーライドするときは常に、hashCode() もオーバーライドしなければなりません。これはJavaにおける規約です。等価なオブジェクトは同じハッシュコードを持たなければなりません。HashMap や HashSet のようなハッシュベースのコレクションは、正しく機能するためにこのルールに依存しています。
@Override
public int hashCode() {
return Objects.hash(name, age);
}Objects.hash() ユーティリティメソッドは、equals() で使用されるのと同じフィールドからハッシュコードを生成します。2つの Person オブジェクトが同じ name と age を持っている場合、それらは等しいとみなされ、同一のハッシュコードを生成します。これにより、コレクション内での一貫した動作が保証されます。
チャレンジ
簡単オブジェクトの等価性を適切に実装した学生記録システムを構築しましょう。2人の学生が、メモリ上の同じオブジェクトであるかどうかにかかわらず、学生IDと名前が同じであれば等しいとみなされる Student クラスを作成します。
コードは2つのファイルに分けて構成します:
Student.java: 3つのプライベートフィールドstudentId(String)、name(String)、gpa(double) を持つ学生を表すクラスを作成します。すべてのフィールドを初期化するコンストラクタと、各フィールドのゲッターメソッドを含めてください。2つの Student オブジェクトが同じ
studentIdかつ同じnameを持っている場合に等しいとみなされるように、equals()メソッドをオーバーライドします。標準的なパターンに従ってください:同一リファレンスのチェック、nullと型のチェック、そして関連するフィールドの比較を行います。equals()で使用したのと同じフィールドを使用して、Objects.hash()を使いhashCode()メソッドをオーバーライドします。ファイルの先頭でjava.util.Objectsをインポートすることを忘れないでください。Main.java: 学生オブジェクトを比較することで、Student クラスを実際に動かしてみましょう。2人の学生を表す6つの入力(1人目の学生のID、名前、GPA、続いて2人目の学生のID、名前、GPA)を受け取ります。両方の Student オブジェクトを作成し、3つの比較を実行して結果を出力します:
Same reference:に続けて、1人目の学生が自分自身と等しいかどうかを出力します(常に true になるはずです)。Content equality:に続けて、1人目の学生が2人目の学生と等しいかどうかを出力します(実装した equals に基づきます)。Hash codes match:に続けて、両方の学生が同じハッシュコードを持っているかどうかを出力します。
6つの入力を受け取ります:1人目の学生のID (String)、1人目の学生の名前 (String)、1人目の学生のGPA (double)、2人目の学生のID (String)、2人目の学生の名前 (String)、2人目の学生のGPA (double)。
2人の学生のIDと名前が一致する場合、equals() が true を返し、かつハッシュコードも一致することに注目してください。これが維持しなければならない規約(contract)です!
チートシート
== 演算子は、オブジェクトが同じ内容を持っているかどうかではなく、2つの参照が同じメモリ位置を指しているかどうかをチェックします。
オブジェクトの内容を比較するには、次のパターンに従って equals() メソッドをオーバーライドします。
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 同じ参照
if (obj == null || getClass() != obj.getClass()) return false; // Nullまたは異なる型
Person person = (Person) obj; // 正しい型にキャスト
return age == person.age && name.equals(person.name); // フィールドを比較
}equals() をオーバーライドするときは常に、hashCode() もオーバーライドしなければなりません。これはJavaの規約です。等価なオブジェクトは同じハッシュコードを持たなければなりません。
@Override
public int hashCode() {
return Objects.hash(name, age);
}equals() で使用したのと同じフィールドで Objects.hash() を使用してください。これにより、HashMap や HashSet のようなハッシュベースのコレクションが正しく動作することが保証されます。
自分で試してみよう
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 1人目の生徒のデータを読み込む
String id1 = scanner.nextLine();
String name1 = scanner.nextLine();
double gpa1 = scanner.nextDouble();
scanner.nextLine(); // 改行文字を読み飛ばす
// 2人目の生徒のデータを読み込む
String id2 = scanner.nextLine();
String name2 = scanner.nextLine();
double gpa2 = scanner.nextDouble();
// TODO: 入力データを使用して2つの Student オブジェクトを作成する
// TODO: "Same reference: " に続けて、 student1 が自身と等しいかどうかを出力する
// TODO: "Content equality: " に続けて、 student1 が student2 と等しいかどうかを出力する
// TODO: "Hash codes match: " に続けて、両方の生徒が同じハッシュコードを持っているかどうかを出力する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4継承
継承の基本 (extends)super キーワードメソッドのオーバーライド (@Override)コンストラクタチェーンObject クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造7特殊メソッドとObjectクラス
toString() メソッドequals() と hashCode()clone() メソッドcompareTo() と ComparableComparator インターフェースまとめ - カスタムソート