検索

キーワード


【Java】equalsメソッドの使い方と hashCode メソッドとの関係を解説!

  • 公開日:2020-10-12 17:15:28
  • 最終更新日:2021-01-26 09:24:11
【Java】equalsメソッドの使い方と hashCode メソッドとの関係を解説!

こんにちは、駆け出しプログラマーの若江です!

ここでは初学者として学習を終えた私からアウトプットの意味も込めて、

equals メソッドと hashCode メソッドについて紹介させていただきます。

できる限り初学者が理解しやすい内容として紹介させていただくので、参考となれば幸いです!


関連記事リンク:データ型の紹介 / インスタンスの紹介 / if文の紹介 / オーバーライドの紹介 / メソッドの紹介 / プリミティブ型と参照型の紹介

equals メソッドと hashCode メソッドの使い方と関係性

equals メソッドは値の比較に使われるメソッドです。

値の比較といえば演算子の「==」も同じく値の比較です。

ここでは == と equals メソッドの違いや equals メソッドの書き方、

equalsメソッドと深い関わりにある hashCode メソッドについて紹介させていただきます。


equals メソッドと == の違い

equals メソッドと == はどちらも値の比較をすることができます。

ただし == はモノ自体の比較を行い、equals メソッドはモノの内容を比較というする違いがあります。

この違いは「同一性」と「同値性」という言葉で表されます。

「同一性」と「同値性」を理解することで、equals メソッドと == の異なる点がよりわかりやすくなります。


同一性

2つの値を比較して「同じもの」であるか(同一性)を判断します。

Java では「 == 」を使って同一性の比較判断をし、一般的にプリミティブ型に対して使われます。

「 == 」は同じものであった場合に「true」、同じものでなかった場合に「false」を返します。


 例えば保持している値 1 と、受け取った値 1 が同一のものか? といったように使われます。

 型などは比較要素に入れず、保持しているものと受け取ったものが 1 かどうかを比較します。

 そのため 1 と 1.0 でも比較結果は「同一」と判断されます。


◆プリミティブ型を == で比較した例

public class EqualsTest {
	public static void main(String[] args) {
		int num = 1;

		if(num == 1.0) {
			System.out.println("true");
		}
	}
}

 1 を代入した num と 1.0 を比較して if 文で true を返しています。


 また、参照型を == で比較することも可能ではあります。

 ただし、参照型が持っている情報は「参照先のアドレス」であるため == で比較した場合は、

 同じ参照先かどうかの比較を行います。

 参照型である String を使ってサンプルコードを見てみましょう。


◆ 参照型を == で比較した例

public class EqualsTest {
	public static void main(String[] args) {
		String str = "a";
		String str2 = str;

		if(str == str2) {
			System.out.println("str と str2 は同じ参照先へアクセスする");
		}
	}
}

 str は "a" を参照するためのアドレスを保持。

 変数 str2 に str を代入することで str も str2 も同じ参照先を見るため == 比較では true を返します。


あくまでも指定された値自身を比較するのであって、その中身を比較することはしません。

もし中身の比較を行いたい場合は「同値性」を確認する「equales メソッド」を使います。


同値性

2つのオブジェクトが「同じ値を保持しているか(同値性)」を比較判断します。

主に参照型に対して「equals メソッド」を使い、参照先の保持する値の同値性を比較判断します。

同一性とは異なり同値性は参照先の内容を比較するため、String のように文字列を比較することも可能です。


◆ String 型の equals メソッド使用例

public class EqualsTest {
	public static void main(String[] args) {

		String str = "abcd";
		String str2 = "abcd";

		if(str.equals(str2)) {
			System.out.println("true"); // 同値であるため「true」を表示
		}
	}
}


同一性は見た目、同値性は内容物を比較する

といった違いをイメージするとわかりやすいかもしれません。



equals メソッドの使い方

先述の equals メソッドは全てのクラスのスーパークラスとなる Object クラスに実装されています。

そのため自作クラスであっても equals メソッドを実装することなく使うことができます。

しかし Object クラスに実装されている equals メソッドは

「比較対象のクラスが自分自身か?」を比較するだけで、中身の比較まではしません。

中身の比較をするには Object クラスの equals メソッドを、作成したクラスにオーバーライドします。

オーバーライドした equals メソッドでは、以下4つの比較処理を上から順に行うことで同値性の確認ができます。



オーバーライドした equals メソッドの処理内容:

 ・比較対象との参照先が同じか?

  参照先が同じだった場合、保持する値は同じであることを意味するため、

  最初に比較対象と自分自身の参照先を確認します。

 ・比較対象は null ではないか?

  比較対象が null であれば内容の評価ができないため、null ではないか確認します。

 ・比較対象は自分自身のクラスのインスタンスか?

  自分自身のクラスのインスタンス以外は、保持している値の型が比較できるものであると保証できないため、

  比較対象が自身と同じクラスから生成されたインスタンスであるかを確認します。

 ・比較対象が保持する値と自分自身が保持する値は異なるか?

  同値性を確認します。



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

equals メソッドの戻り値は boolean 型で評価の結果が同値であれば true、異なれば false を返します。

先ほど紹介した equals メソッドの4つの処理内容を true/false の評価を返す形で、

equals メソッドを使いたいクラスにオーバーライドします。

サンプルコードで equals メソッドをオーバーライドする書き方を見てみましょう。


◆ equals メソッドのオーバーライド例

public class EqualsObject {

	// フィールド
	private int num;

	// コンストラクター
	EqualsObject(int num){
		this.num = num;
	}

	// メソッド
	@Override
	public boolean equals(Object obj) {

		// obj の参照先が自分自身と同じであれば true を返す
		if(this == obj) {
			return true;
		}

		// obj が null であれば false を返す
		// obj が 自分自身のクラスから生成されたインスタンスでなければ false を返す
		if(!(obj instanceof EqualsObject)) {
			return false;
		}

		// obj の型を Object 型から EqualsObject 型へキャスト
		EqualsObject test = (EqualsObject)obj;

		// obj をキャストした test が保持する値が自分自身の保持する値と異なれば false を返す
		if(num != test.num) {
			return false;
		}

		// 上記 if 文を通過した場合は、obj が同値であるため true を返す
		return true;
	}
}

 ※ instanceof は評価対象が null であった場合に false を返す機能を持っています。

比較する値が文字列の場合は Objects クラスの equals メソッドを使うことができます。

import java.util.Objects;
public class EqualsObject {

	private String str;

	EqualsObject(String str){
		this.str = str;
	}

	@Override
	public boolean equals(Object obj) {

		if(this == obj) {
			return true;
		}

		if(!(obj instanceof EqualsObject)) {
			return false;
		}

		EqualsObject test = (EqualsObject)obj;

		// 文字列の比較。文字列が異なる場合は false を返す
		if(!Objects.equals(str, test.str)) {
			return false;
		}

		return true;
	}
}

※上記 equals メソッドの記述方法は一例であり、プログラマーによって書き方は異なります。


EqualsObject クラスに equals メソッドのオーバーライドができたので、

EqualsObject インスタンスの同値性を確認できるメソッドが使えるようになりました。


EqualsObject クラスのインスタンスを3つ作成して、実際に equals メソッドの使い方を確認してみましょう。

サンプルコードでは値が同じインスタンスの比較と、値が異なるインスタンスの比較を int 型で行っています。


◆ equals メソッドの利用例

public class Test {
	public static void main(String[] args) {

		EqualsObject test = new EqualsObject(1);
		EqualsObject test2 = new EqualsObject(1);
		EqualsObject test3 = new EqualsObject(2);

		// 中身が 1 と 1 で同値のため if 文へ入る
		if(test.equals(test2)) {
			System.out.println("test test2: true");
		}

		// 中身が 1 と 2 で値が異なるため if 文へ入らない
		if(test.equals(test3)) {
			System.out.println("test test3: true");
		}
	}
}


equals メソッドを使用することができました。

しかし Java のシステム上、これで常に正しくオブジェクトの評価ができるようになったというわけではありません。

hashCode という機能と equals メソッドは深く関わり合っており、

equals メソッドをオーバーライドする際は、

この hashCode の値を得るために hashCode メソッドもオーバーライドする必要があります。


余談

equals メソッドが同値性のチェックを行う範囲は開発者に委ねられます。

例えば先ほど例に出したサンプルコードでは instanceof を使ってインスタンスの比較を行いましたが、

この場合継承関係にあるクラスの型であれば、別クラスの型と比較しても true を返します。

より厳密にチェックを行いたい場合は、Object クラスが持つ「getClass メソッド」を使うことで

同じクラスの型であるかを比較することができます。

@Override
public boolean equals(Object obj) {

	if(this == obj) {
		return true;
	}

	// instanceof を使わないため null 比較を追加
	if(obj == null) {
		return false;
	}

	// getClass メソッドでクラスの型を比較
	if(getClass() != obj.getClass()) {
		return false;
	}

	EqualsHashCodeObject test = (EqualsHashCodeObject)obj;

	if(num != test.num) {
		return false;
	}

	if(!Objects.equals(str, test.str)) {
		return false;
	}

	return true;
}



hashCode メソッドとは?

hashCode は int 型の整数を各オブジェクトへ発番して管理する役割をします。

インスタンスやインスタンスが保持する値などによってそれぞれ hashCode が発番され、

発番された管理番号は可能な限り重複しないように設計されていますが、

番号が重複する可能性もあるため、番号重複を考慮して扱わなくてはなりません。


hashCode は Object クラスに hashCode を発番するための「hashCode メソッド」が実装されているため、

自作クラスにオーバーライドしなくても自動で発番されるようになっています。

また発番以外にも hashCode メソッドを使うことでオブジェクトが持つ番号を調べることもできます。


hashCode と equals メソッドの関係

オブジェクトの管理に使われる hoshCode と先述の equals メソッドは深い関わりを持っています。

Java で用意されている一定のクラスやメソッドは以下で紹介する、

hashCode と equals メソッドのルール」が守られている前提で設計がされています。



hashCode と equals メソッドのルール:

 ●同じオブジェクトに対して hashCode メソッドを複数回呼び出した場合は同一の値を返す。


 ●equals メソッドで比較した2つのオブジェクトの結果が true の場合、

  hashCode の返す値が2つのオブジェクト間で同じ値となる。


 ●equals メソッドで比較した2つのオブジェクトの結果が false の場合、

  hashCode の返す値は2つのオブジェクト間で同じでも違っても良い。


 ●hashCode の返す値が2つのオブジェクト間で異なる場合、

  equals メソッドで2つのオブジェクトを比較した結果は false を返す。



このようなルールに従った equals メソッドと hashCode メソッドが必要となりますが、

Object クラスが持つ hashCode メソッドは自作クラスに対して適した hashCode メソッドではないため、

クラスの equals メソッドを正しく動作させるには、hashCode メソッドもオーバーライドしなくてはなりません。


hashCode メソッドのオーバーライド

hashCode メソッドによって生成される番号は可能な限りユニークな番号にする必要があります。

そのためインスタンスの保持する値が変更されれば、hashCode の値も変更されることが望ましいです。

ちなみに各インスタンスの hashCode の値がプロジェクト実行毎に変更されても構いません。


基本的な hashCode メソッドのオーバーライド方法は、

「31」に「0を除く素数のローカル変数」を掛け合わせた積に

「インスタンスフィールドが保持する値」を足した和の式が使われます。

・hashCode メソッドの発番基本構文: int 変数名 = 31 * 0以外の素数 + インスタンスフィールドの値;


まずはインスタンスフィールドの値に int 型の整数を保持したクラスでの hashCode の使用例を

上記基本構文を元に実際のサンプルコードで見てみましょう。


◆ hashCode メソッドのオーバーライド例

public class HashCodeObject {
	int num = 10;

	@Override
	public int hashCode() {
		int result = 7;
		result = 31 * result + num;
		
		return result;
	}
}


上記で int 型の hashCode の値が返されます。

上記例では int 型のインスタンスフィールドを渡しましたが、String 型の文字列を渡すことも可能です。

hashCode メソッドが受け取った String 型の値をクラスに対応した hashCode の値で返す場合は、

インスタンスフィールドの文字列から hashCode の値を取得して利用します。

ただし文字列が null であった場合は取得できる hashCode がないので、null は 0 とします。


◆ String 型インスタンスフィールドの hashCode メソッドオーバーライド例

public class HashCodeObject {
//	int num = 10;
	String str = "test";

	@Override
	public int hashCode() {
		int result = 7;
		result = 31 * result + ((str == null)? 0 : str.hashCode());
		
		return result;
	}
}


31 に0以外の素数をかける部分は同じで、それ以降の()内が先ほどの例と異なります。

「(str == null)」 が true なら「0」、false なら「str.hashCode()」へ移行します。


これで hashCode のオーバーライドができました。


余談

hashCode が返す値を1にしてしまうのも、ルールに反していないため可能です。

public int hashCode() {
    return 1;
}

このようにすると hashCode を利用している equals メソッドのような機能が

このクラスのインスタンスの hashCode を見たときは、

どのインスタンスも1となるため機能の精密度が低下します。

しかしこのような機能を使わない場合は hashCode が1であっても差し支えありません。

状況によっては上記のような書き方ができる場合もあります。



equals メソッドと hashCode メソッドの記述

equals メソッドとhashCode メソッドの紹介をしましたので、

自作クラスへ正しく動作する equals メソッドのオーバーライドができるようになりました。

インスタンスを3つ生成して equals メソッドが true を返す場合と false を返す場合を確認しましょう。


◆ クラスへの equals メソッド、hashCode メソッドオーバーライド例

【オーバーライドするクラス】 equals メソッドと hashCode メソッドをオーバーライド

public class EqualsHashCodeObject {

	// インスタンス変数
	private int num;
	private String str;

	// コンストラクター
	EqualsHashCodeObject(int num, String str){
		this.num = num;
		this.str = str;
	}

	// equals メソッドをオーバーライド
	@Override
	public boolean equals(Object obj) {

		if(this == obj) {
			return true;
		}

		if(!(obj instanceof EqualsHashCodeObject)) {
			return false;
		}

		EqualsHashCodeObject test = (EqualsHashCodeObject)obj;

		if(num != test.num) {
			return false;
		}

		if(!Objects.equals(str, test.str)) {
			return false;
		}

		return true;
	}

	// hashCode メソッドをオーバーライド
	@Override
	public int hashCode() {
		int result = 7;
		result = 31 * result + num;
		result = 31 * result + ((str == null)? 0 : str.hashCode());

		return result;
	}
}

【実装クラス】

public class Test {
	public static void main(String[] args) {

		// インスタンスを3つ生成
		EqualsHashCodeObject test = new EqualsHashCodeObject(1,"abc");
		EqualsHashCodeObject test2 = new EqualsHashCodeObject(1,"abc");
		EqualsHashCodeObject test3 = new EqualsHashCodeObject(2,"abc");

		// それぞれの hashCOde の値を確認
		System.out.println(test.hashCode());
		System.out.println(test2.hashCode());
		System.out.println(test3.hashCode());

		// 同じ内容なため if 文へ入る
		if(test.equals(test2)) {
			System.out.println("test と test2 は同じ内容");
		}
		// 異なる内容なため if 文に入らない
		if(test.equals(test3)) {
			System.out.println("test と test3 は同じ内容");
		}
	}
}


test と test2 は同じ hashCode の値を保持していて、内容も 1, "abc" で同じため true を返し、

test と test3 は異なる hashCode の値を保持しているため false を返しています。

equals メソッドで比較できていることが確認できました。


equals メソッドと hashCode メソッドの自動生成

もし使っている開発環境が Eqlipce であれば、equals メソッドと hashCode メソッドの自動生成が可能です。


【equals メソッドと hashCode メソッドの自動生成方法】

⑴ equals メソッド、 hashCode メソッドを生成したいクラス内で右クリック

⑵「ソース」を選択

 ※もしくは Eqlipce ウィンドウ上部のバーから「ソース」を選択

⑶「hashCode() および equals() の生成 をクリック」

⑷ hashCode() と equals() で利用する引数をフィールドから選択

⑸「生成」をクリック


上記フローで equals メソッドと hashCode メソッドの自動生成ができます。

※自動生成される equals メソッドは getClass メソッドでクラスの型まで比較する記述がされています。



まとめ

equals メソッドは広い範囲の様々な場面で活躍するメソッドです。

equals メソッドを理解するためには hashCode メソッドも理解しなくてはなりませんが、

プログラマーとしての恩恵は大きいはずですので、しっかり押さえておきましょう。


関連記事リンク

データ型の紹介 / インスタンスの紹介 / if文の紹介 / オーバーライドの紹介 / メソッドの紹介 / プリミティブ型と参照型の紹介


【著者】

若江

30代で異業種となるIT業界へ転職した駆け出しのプログラマです。これまで主に Java や Ruby、HTML/CSS を使って学習を目的としたショップサイトや掲示板サイトの作成を行いました。プログラマとしての経験が浅いからこそ、未経験者の目線に近い形で基礎の紹介をしていきたいと思います。

よく読まれている記事
【Java】JSPでタグライブラリを使う(JSTL)

【Java】JSPでタグライブラリを使う(JSTL)

こんにちは。エンジニアの新田です!ここでは、システムエンジニアとして働いている私が、システム開発手法や開発言語について紹介していこうと思います。今回は、JSPの標準タグライブラリ「JSTL」について紹介します。Javaについて勉強している方、Webアプリケーションを構築したいと思っている方の参考になれば幸いです!関連記事リンク: 【Java】JSPの基本的な構文/【Java】JSPのアクションタグ

【Java】Stringクラス文字列を操作するメソッドの使い方まとめ!実例も紹介!

【Java】Stringクラス文字列を操作するメソッドの使い方まとめ!実例も紹介!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!そもそもStringとは何?『 String 』... Java言語において文字列のデータ型を指します。基本デ

【Java】文字列の置き換え(String#format)!エスケープシーケンスのまとめも!!

【Java】文字列の置き換え(String#format)!エスケープシーケンスのまとめも!!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!Stringクラスformatメソッドの文字列整形【java.utilパッケージ】Formatterクラスfo

【Java】文字列格納後に変更可能!?StringBufferクラスとStringBuilderクラス!

【Java】文字列格納後に変更可能!?StringBufferクラスとStringBuilderクラス!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者にも✅、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!文字列を扱う3つのクラス【java.langパッケージ】java.langパッケージの文字列を扱うクラスにはS

【Java】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

【Java】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!プリミティブ型と参照型プログラム開発では型を持った変数を使ってデータのやり取りをしますが、データ型によって仕様