Java

【Java】hashCode・equalsメソッドの使い方

こんにちは、ともです。

今回は、java.lang.Objectクラスが持っている、hashCodeメソッドについての理解を整理したいと思います。

最近、JavaGoldの黒い本を購入していまして勉強をはじめました。

JavaSilverの黒い本を一通り終えて、まだSilverの資格は取っていないんですが会社でJavaを書いているため早めにJavaの仕様を把握したいという思いで、JavaGoldの教科書を進めてみます。

そこで初めてhashCodeというメソッドに出会いました。

hashCodeについて調べると、hashCodeとequalsメソッドには関係性があることが分かりました。そこで調べて理解したことをまとめます。

hashCodeメソッドとは

ドキュメントを参照します。

Javaではオブジェクトの識別にint型のハッシュ値が使用されています。

このハッシュ値はメモリアドレスを基に生成した値です。

オブジェクトを検索する際のパフォーマンスを向上させるために利用されます。

では次に、どのようなハッシュコードが設定されているのか確認します。

異なる値のオブジェクトのハッシュコードの確認

異なる値のオブジェクトに対して、どのようなハッシュコードが生成されるか確認しました。

public class HashCodeCheck {
	public static void main(String[] args) {
		String st1 = new String("st1");
		String st2 = new String("st2");
		class Test {}
		Test test = new Test();
		Object obj = new Object();
		System.out.println(st1.hashCode());//114160
		System.out.println(st2.hashCode());//114161
	        System.out.println(obj.hashCode());//366712642
		System.out.println(test.hashCode());1829164700
	}
}

結果としては、上記のようなハッシュコードが確認できました。

4つ全て異なる値ですので、異なるハッシュコードが出力されていることが分かります。

同じ値のオブジェクトのハッシュコード

同じ値のオブジェクトに対して、どのようなハッシュコードが生成されるか確認しました。

public class HashCodeCheck {
	public static void main(String[] args) {
		String st1 = new String("st");
		String st2 = new String("st");
		System.out.println(st1.hashCode());//114161
		System.out.println(st2.hashCode());//114161
	}

}

同じ”st”という値の文字列オブジェクトは同じハッシュコードとなりました。

次に途中で値を変更させてみます。

public class HashCodeCheck {
	public static void main(String[] args) {
		String st1 = new String("st2");
		String st2 = new String("st2");
		System.out.println(st1.hashCode());//114161
		System.out.println(st2.hashCode());//114161

		st2 = "othre";
		System.out.println(st2.hashCode());//106070116
	}

}

途中で異なる値を代入し、同値では無くすと異なるハッシュコードに変わりました。

少し前に、「アドレスを元にハッシュコードは生成されている」と記載しました。

つまり、同じ値の場合は同じアドレスを参照してメモリを節約していることが分かります。

そして、異なった値になって初めて別のアドレスを参照するようになったため、異なるハッシュコードが生成された訳です。

hashCodeとequals

  • equals(Object)メソッドに従って2つのオブジェクトが等しい場合は、2つの各オブジェクトに対するhashCodeメソッドの呼出しによって同じ整数の結果が生成される必要があります。
  • equals(java.lang.Object)メソッドに従って2つのオブジェクトが等しくない場合は、2つの各オブジェクトに対するhashCodeメソッドの呼出しによって異なる整数の結果が生成される必要はありません

参照元:ドキュメント

ドキュメントにも記述されていますが、

同値のオブジェクトの場合 同じハッシュコードを必ず出力
異なる値のオブジェクトの場合 ハッシュコードは同じでも異なっていてもどちらでも良い。

(異なるに越したことはない)

と書かれています。

つまり、equalsメソッドをオーバライドした場合は、hashCodeもオーバライドし、同値の場合は同じハッシュコードを返すようにする必要があります。

サンプルコード

hashCodeをオーバライドしない悪い例

同値にもかかわらず、ハッシュIDが異なる悪いサンプルをまず示します。

public class Main {

	public static void main(String[] args) {
		class Student {
			private int id;
			private String name;
			public Student(int id, String name) {
				this.id = id;
				this.name = name;
			}

			public int getId() {return this.id;}
			public String getName() {return this.name;}

			//IDで同値判定を行う
			public boolean equals(Object obj) {
				if(obj instanceof Student) {
					Student std = (Student)obj;
					if(this.id == std.getId()) {
						return true;
					}
				}
				return false;
			}

			// hashCodeのオーバライドをしていない
		}

		// IDが同じ、つまり同値のStudentクラスのインスタンスを生成
		Student student1 = new Student(1, "st1");
		Student student2 = new Student(1, "st2");

		if(student1.equals(student2)) {
			System.out.println("同値です");
			System.out.println("student1のハッシュコード:" + student1.hashCode());//366712642
			System.out.println("student2のハッシュコード:" + student2.hashCode());//1829164700
		}
	}
}

equalsメソッドのオーバーライドにより同値と判定されているにもかかわらず、ハッシュコードが異なります。これではハッシュコードで同値判定することができません。

hashCodeをオーバライドしている正しい例

public class Main {

	public static void main(String[] args) {
		class Student {
			private int id;
			private String name;
			public Student(int id, String name) {
				this.id = id;
				this.name = name;
			}

			public int getId() {return this.id;}
			public String getName() {return this.name;}

			//IDで同値判定を行う
			public boolean equals(Object obj) {
				if(obj instanceof Student) {
					Student std = (Student)obj;
					if(this.id == std.getId()) {
						return true;
					}
				}
				return false;
			}

			// hashCodeのオーバライドをして、IDをハッシュコードとしている
			public int hashCode() {
				return getId();
			}
		}

		// IDが同じ、つまり同値のStudentクラスのインスタンスを生成
		Student student1 = new Student(1, "st1");
		Student student2 = new Student(1, "st2");

		if(student1.equals(student2)) {
			System.out.println("同値です");
			System.out.println("student1のハッシュコード:" + student1.hashCode());//1
			System.out.println("student2のハッシュコード:" + student2.hashCode());//1
		}
	}
}

このプログラムではhashCodeをオーバライドし、同値の物は同じハッシュコードを返すようにしています。

まとめ

ハッシュコードについて調べるとequalsメソッドに行きつき理解が深まりました。

今までequalsメソッドをオーバライドした時に、hashCodeメソッドをオーバライドしていなかったことを反省します。