Java道|equals()の使い方と役割

見た目が似ていても、本当に同じオブジェクトとは限りません。
equals() を理解すると、Javaで「同じ」と判断する基準がはっきり見えてきます。

Javaでオブジェクトを扱っていると、
この2つの変数は、同じオブジェクトを指しているのか
を調べたい場面があります。

たとえば、鬼滅の刃風にたとえると、2枚の隊士札があるとします。
その2枚が、同じ隊士本人を指しているのか、それとも名前や見た目が似ているだけの別の隊士を指しているのかを確認したい場面です。

このようなときに使うのが equals() です。

equals() は、Object クラスに用意されている基本メソッドのひとつです。
Javaのすべてのクラスは最終的に Object クラスを継承しているため、自分で equals() を書いていないクラスでも、最初から equals() を使えます。

ただし、ここで大切なのは、Object クラス由来の equals() が、最初から「中身が似ているか」を見てくれるわけではないという点です。

Object クラス由来の equals() は、基本的には、
2つの変数が同じオブジェクトを指しているかどうか
を調べます。

鬼滅の刃風にたとえると、equals() は次のような確認です。

比較したいことequals() の基本的な見方
同じ名前の隊士かそれだけでは判断しない
同じ階級の隊士かそれだけでは判断しない
同じ実体の隊士を指しているかここを見る
別々に作られた隊士か別物として判断する

つまり equals() の基本は、見た目や中身の似ている・似ていないではなく、指している実体が同じかどうか です。

equals() は何をするメソッドなのか

equals() は、あるオブジェクトと、引数として渡したオブジェクトが同じかどうかを調べるメソッドです。

基本的な形は次のようになります。

object1.equals(object2)

この書き方では、object1 と object2 が同じオブジェクトかどうかを調べます。

戻り値は boolean 型です。

戻り値意味
true同じオブジェクトを指している
false違うオブジェクトを指している

鬼滅の刃風にたとえると、slayer1 と slayer3 が同じ隊士本人を指しているなら true です。
一方、slayer1 と slayer2 が別々に作られた隊士なら false です。

ここでのポイントは、
同じような情報を持っているかどうかではなく、同じ実体かどうかを見る
ということです。

↓クリックすると拡大表示されます。

同じオブジェクトとはどういう意味か

Javaでは、変数の中にオブジェクトそのものが丸ごと入っているわけではありません。

正確には、変数はオブジェクトを指しています。
そのため、複数の変数が同じオブジェクトを指すこともあります。

たとえば、次のようなイメージです。

DemonSlayer slayer1 = new DemonSlayer();
DemonSlayer slayer3 = slayer1;

この場合、新しく作られた DemonSlayer オブジェクトは1つだけです。
slayer3 = slayer1; は、新しいオブジェクトを作っているのではありません。

slayer1 が指している同じオブジェクトを、slayer3 も指すようにしているだけです。

鬼滅の刃風にたとえると、1人の隊士本人に対して、2枚の札が向いているようなものです。

変数指しているもの
slayer11人目の DemonSlayer オブジェクト
slayer3slayer1 と同じ DemonSlayer オブジェクト

この場合、slayer1.equals(slayer3) は true になります。

一方で、次のように new を2回使うと、別々のオブジェクトが作られます。

DemonSlayer slayer1 = new DemonSlayer();
DemonSlayer slayer2 = new DemonSlayer();

この場合、たとえ中身の初期値が同じでも、slayer1 と slayer2 は別々のオブジェクトです。

変数指しているもの
slayer11つ目の DemonSlayer オブジェクト
slayer22つ目の DemonSlayer オブジェクト

そのため、Object クラス由来の equals() では、slayer1.equals(slayer2) は false になります。

Object クラスの equals() が使える理由

equals() は、自分のクラスに毎回書かなければ使えないメソッドではありません。

Javaのすべてのクラスは、最終的に Object クラスを継承しています。
そして、equals() は Object クラスに用意されています。

そのため、次のような自作クラスでも equals() を使えます。

クラスequals() を使える理由
DemonSlayerObject を継承しているから
PillarSlayerDemonSlayer を通じて Object につながるから
NichirinSwordObject を継承しているから
MissionScrollObject を継承しているから

つまり、equals() は、Javaのオブジェクトが共通して持っている基本的な比較メソッドです。

鬼滅の刃風にたとえると、隊士、日輪刀、任務書など、どんな種類のオブジェクトでも、Java世界の共通ルールとして「同じ実体かどうか」を確認できるということです。

↓クリックすると拡大表示されます。

同じオブジェクトかどうかを調べる

ファイル名:Sample8.java

class DemonSlayer
{
    protected String name;
    protected String rank;

    public DemonSlayer()
    {
        name = "隊士未登録";
        rank = "階級未設定";
        System.out.println("鬼殺隊士を作成しました。");
    }
}

class Sample8
{
    public static void main(String[] args)
    {
        DemonSlayer slayer1 = new DemonSlayer();
        DemonSlayer slayer2 = new DemonSlayer();

        DemonSlayer slayer3;
        slayer3 = slayer1;

        boolean bl1 = slayer1.equals(slayer2);
        boolean bl2 = slayer1.equals(slayer3);

        System.out.println("slayer1とslayer2が同じか調べたところ" + bl1 + "でした。");
        System.out.println("slayer1とslayer3が同じか調べたところ" + bl2 + "でした。");
    }
}

実行結果

鬼殺隊士を作成しました。
鬼殺隊士を作成しました。
slayer1とslayer2が同じか調べたところfalseでした。
slayer1とslayer3が同じか調べたところtrueでした。

Sample8.java の流れ

このプログラムでは、まず次の2行で DemonSlayer オブジェクトを2つ作っています。

DemonSlayer slayer1 = new DemonSlayer();
DemonSlayer slayer2 = new DemonSlayer();

new DemonSlayer() を2回実行しているため、別々のオブジェクトが2つ作られます。

変数指しているもの
slayer11つ目の DemonSlayer オブジェクト
slayer22つ目の DemonSlayer オブジェクト

この時点で、slayer1 と slayer2 は別々のオブジェクトです。

そのため、次の比較は false になります。

boolean bl1 = slayer1.equals(slayer2);

次に、slayer3 を用意して、slayer1 を代入しています。

DemonSlayer slayer3;
slayer3 = slayer1;

これは、新しいオブジェクトを作っているわけではありません。
slayer1 が指しているオブジェクトを、slayer3 も指すようにしています。

変数指しているもの
slayer11つ目の DemonSlayer オブジェクト
slayer3slayer1 と同じ DemonSlayer オブジェクト

そのため、次の比較は true になります。

boolean bl2 = slayer1.equals(slayer3);

equals() は何を見て true と false を返しているのか

Sample8.java の比較結果を整理すると、次のようになります。

比較結果理由
slayer1.equals(slayer2)false別々に作られたオブジェクトだから
slayer1.equals(slayer3)true同じオブジェクトを指しているから

この例から分かるのは、Object クラス由来の equals() が、基本的には 同じ実体を指しているかどうか を見ているということです。

ここで、slayer1 と slayer2 は、どちらも DemonSlayer クラスから作られています。
初期値も同じです。

フィールドslayer1slayer2
name隊士未登録隊士未登録
rank階級未設定階級未設定

それでも、slayer1.equals(slayer2) は false です。

理由は、同じ値を持っているように見えても、別々に作られたオブジェクトだからです。

鬼滅の刃風にたとえると、同じような初期登録状態の隊士が2人いても、それぞれ別の隊士本人なら「同じ存在」とは言えない、ということです。

鬼滅の刃風に考える equals() の感覚

equals() は、鬼滅の刃風にたとえると、
この2つの札は、本当に同じ隊士本人を指しているのか
を確認するようなものです。

たとえば、次のように考えると分かりやすいです。

状況equals() の結果
slayer1 は水月本人を指している。slayer3 も同じ水月本人を指しているtrue
slayer1 は水月を指している。slayer2 は別に作られた別の隊士を指しているfalse
slayer1 と slayer2 は同じ名前でも、別々に作られた隊士である基本の equals() では false

ここで大切なのは、
同じクラスから作られているかどうかだけでは true にならない
という点です。

同じ DemonSlayer クラスから作られていても、new によって別々に作られたオブジェクトなら、基本の equals() では false になります。

equals() が便利な場面

equals() は、複数の変数が同じオブジェクトを指しているかどうかを調べたいときに便利です。

たとえば、次のような場面です。

場面equals() が役立つ理由
同じオブジェクトを別名で管理していないか確認したい2つの変数が同じ実体を指しているか分かる
配列やリストに同じオブジェクトがあるか確認したいすでに登録済みの実体か確認できる
メソッドの引数で渡されたオブジェクトを確認したい自分が持っているオブジェクトと同じか分かる
オブジェクトの参照関係を学びたい変数と実体の違いを理解しやすい

鬼滅の刃風にたとえると、隊士一覧の中で、
この札は、すでに登録済みのあの隊士本人を指しているのか
を確認するような使い方です。

String クラスの equals() は少し違う

ここで大切なのが、equals() はクラスによってオーバーライドされていることがある、という点です。

代表例が String クラスです。

String クラスの equals() は、Object クラス由来の「同じオブジェクトかどうか」をそのまま使うのではなく、文字列の内容が同じかどうか を調べるように作り直されています。

たとえば、次のような2つの文字列があるとします。

String word1 = new String("水の呼吸");
String word2 = new String("水の呼吸");

word1 と word2 は、別々に作られた String オブジェクトです。

しかし、文字列の内容は同じです。

word1.equals(word2)

この場合、String クラスの equals() では true になります。

比較Object 由来の考え方String の equals()
別々のオブジェクトだが内容が同じ文字列別物と見る文字列内容が同じなので true

これは、String クラスが equals() を文字列比較用にオーバーライドしているからです。

なぜ String クラスでは内容を比較するのか

文字列では、「同じオブジェクトかどうか」よりも、「文字として同じ内容かどうか」のほうが大切になる場面が多いです。

たとえば、鬼滅の刃風に考えると、2つの技名があるとします。

変数内容
technique1水の呼吸
technique2水の呼吸

この2つが別々の String オブジェクトだったとしても、技名としては同じと判断したいことが多いです。

そのため、String クラスでは equals() をオーバーライドして、文字列の内容を比較するようにしています。

つまり、equals() はクラスによって「同じ」の意味を変えられるということです。

Object の equals() と String の equals() の違い

Object クラス由来の equals() と、String クラスの equals() を比べると、違いがはっきりします。

比較対象equals() の基本的な意味
Object クラス由来の equals()同じオブジェクトを指しているかを見る
String クラスの equals()文字列の内容が同じかを見る

この違いはとても重要です。

Object の equals() は、
同じ実体かどうか
を見ます。

String の equals() は、
文字列の内容が同じかどうか
を見ます。

鬼滅の刃風にたとえると、次のようになります。

equals()鬼滅の刃風のイメージ
Object の equals()同じ隊士本人かどうかを見る
String の equals()技名や名前の文字が同じかどうかを見る

このように、equals() はただの比較メソッドではなく、そのクラスにとって「同じ」とは何かを表すメソッドでもあります。

ここから見える大事なこと

equals() を学ぶときに大切なのは、次の2つの見方です。

見方内容
Object クラス由来の基本の equals()同じオブジェクトかどうかを見る
クラスでオーバーライドされた equals()そのクラスに合った「同じ」の意味で比較する

今回の Sample8.java では、DemonSlayer クラスに equals() を定義していません。
そのため、Object クラス由来の equals() が使われています。

つまり、slayer1.equals(slayer2) は、name や rank の値が同じかどうかではなく、同じオブジェクトを指しているかどうかを見ています。

一方で、String クラスのように equals() をオーバーライドしているクラスでは、内容を比較するように動きます。

この考え方は、オブジェクト指向らしい大切な考え方です。

クラスごとに、「何をもって同じとするか」を自分で定義できるからです。

図:equals() が true になる場合と false になる場合

↓クリックすると拡大表示されます。

この図が示していること

この図では、equals() が true になる場合と false になる場合を比較しています。

上側では、slayer1 と slayer3 が同じ DemonSlayer オブジェクトA を指しています。
そのため、slayer1.equals(slayer3) は true になります。

下側では、slayer1 は DemonSlayer オブジェクトA を指し、slayer2 は別の DemonSlayer オブジェクトB を指しています。
そのため、slayer1.equals(slayer2) は false になります。

この図から分かることは、Object クラス由来の equals() が、見た目や初期値ではなく、同じ実体を指しているかどうかを見ているということです。

図:Object の equals() と String の equals() の違い

↓クリックすると拡大表示されます。

この図が示していること

この図では、Object クラス由来の equals() と、String クラスの equals() の違いを整理しています。

左側では、Object 由来の equals() が、同じオブジェクトを指しているかどうかを見ています。
そのため、別々に作られた DemonSlayer オブジェクトなら false になります。

右側では、String クラスの equals() が、文字列の中身を見ています。
そのため、別々の String オブジェクトでも、文字列の内容が同じなら true になります。

この図から分かることは、equals() はクラスによって「同じ」の意味をオーバーライドできるということです。

鬼滅の刃風に equals() を整理する

equals() は、鬼滅の刃風にたとえると、
2つの札が同じ隊士本人を指しているかを確認するためのメソッド
です。

Object クラス由来の equals() は、同じ実体かどうかを見ます。

比較判断
同じ隊士本人を指しているtrue
別々に作られた隊士を指しているfalse
名前や階級が似ているだけ基本の equals() では true とは限らない

一方で、String クラスのように equals() をオーバーライドしているクラスでは、内容を見て比較することがあります。

クラスequals() の考え方
Object 由来の基本同じ実体かどうか
String文字列の内容が同じかどうか
自作クラスでオーバーライドした場合そのクラスに合った同じの基準

つまり equals() は、単に比較するだけのメソッドではありません。
オブジェクト指向では、そのクラスにとって何を同じとみなすのか を表す重要なメソッドです。

この内容で押さえておきたいポイント

ポイント内容
equals()2つのオブジェクトが同じかどうかを調べるメソッド
定義元Object クラス
すべてのクラスで使える理由すべてのクラスは Object を継承しているから
Object 由来の equals()基本的には同じオブジェクトを指しているかを見る
new を2回使った場合別々のオブジェクトなので false になる
別変数に同じオブジェクトを代入した場合同じ実体を指すので true になる
String の equals()文字列の内容が同じかどうかを見る
大切な考え方クラスによって「同じ」の意味をオーバーライドできる

equals() を理解すると、Javaでオブジェクトを比較するときに、何を見て true や false が返っているのかが分かるようになります。

特に大切なのは、Object クラス由来の equals() は、基本的には「中身が似ているか」ではなく「同じ実体を指しているか」を見ているという点です。