Java入門|equals()の使い方と役割(オブジェクト比較のしくみ)

見た目が似ているだけでは、本当に同じ戦士とは言えない。
equals() を理解すると、オブジェクトの「同じ」をどう判断するのかがはっきり見えてくる。

Java では、オブジェクトをたくさん扱うようになると、
「この 2 つは同じものなのか」
を調べたくなる場面がよく出てきます。

たとえば、同じ戦士を別の変数で見ているのか、それとも似ているだけの別の戦士なのかを区別したいことがあります。こういうときに使うのが equals() メソッドです。equals() は Object クラスに用意されている基本メソッドのひとつで、Java のすべてのクラスで使えます。

ドラゴンボールでたとえると、これは
「この 2 人の戦士は本当に同じ実体なのか」
を確かめる場面に近いです。

たとえば、

  • 悟空を指している変数が 2 つあるだけなのか
  • それとも別々に作られた 2 人の戦士なのか

を見分けたいわけです。

ここで大切なのは、equals() が最初から「中身が似ているか」を見るとは限らないことです。Object クラス由来の equals() は、基本的には 同じオブジェクトを指しているかどうか を調べるためのものです。

今回は、この equals() の使い方と役割を、ドラゴンボールの世界観に置きかえながら、やさしく丁寧に整理していきます。特に、

  • equals() は何を比べているのか
  • 同じオブジェクトと、似ているだけの別オブジェクトはどう違うのか
  • Object クラスの equals() が持つ意味
  • String クラスではなぜ違う動きになるのか

を順番に見ていきます。

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

equals() は、2 つの変数が指しているオブジェクトが同じであるかどうかを調べるメソッドです。
同じオブジェクトを指している場合は true、違うオブジェクトを指している場合は false を返します。

つまり、最初の基本としては、

  • 2 つの変数がまったく同じ実体を見ているなら true
  • それぞれ別に作られたオブジェクトなら false

という考え方です。

ドラゴンボールで言えば、

  • warrior1 と warrior3 が、どちらも同じベジータ本人を見ているなら true
  • warrior1 と warrior2 が、それぞれ別に作られた戦士なら false

という感覚です。

ここでのポイントは、
見た目が似ているかどうかではなく、指している先が同じかどうか
を見ることです。

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

ここはとても大事なので、少し丁寧に見ておきましょう。

Java では、変数の中にオブジェクトそのものがそのまま入っていると考えるより、変数はオブジェクトを指している と考えるほうがわかりやすいです。

たとえば、先にオブジェクトを1つ作っておいて、そのあと別の変数に同じものを代入すると、2つの変数は同じオブジェクトを指すことになります。

ドラゴンボールでたとえると、

  • 1 人のベジータがいる
  • それを別の角度から呼ぶ札が 2 枚ある

ようなものです。

札が 2 枚あっても、指している先は 1 人です。
この場合 equals() は true になります。

反対に、見た目や設定が似ていても、別に作られたオブジェクトなら、それは別物です。
札が違う戦士を指しているなら、equals() は false になります。

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

equals() は、自分のクラスに毎回書かなければ使えないわけではありません。
Java のクラスはすべて Object クラスを継承しているので、equals() も最初から使えます。

つまり、

  • Saiyan クラス
  • EliteSaiyan クラス
  • ほかのどんな自作クラス

でも、Object クラスから受け継いだ equals() をそのまま呼び出せます。

これはかなり便利です。
特別な準備をしなくても、「同じオブジェクトかどうか」を調べる基本機能が最初から使えるからです。

サンプルプログラム:equals() の動きを確認

ここでは、ドラゴンボール風のクラスで equals() の動きを確認できる例を見ていきます。
内容は、戦士オブジェクトを 2 つ作り、さらにもう 1 つの変数に同じオブジェクトを代入して比較する流れです。

ファイル名:Sample8.java

class Saiyan
{
    protected String name;
    protected int power;

    public Saiyan()
    {
        name = "戦士なし";
        power = 0;
        System.out.println("サイヤ人を作成しました。");
    }
}

class Sample8
{
    public static void main(String[] args)
    {
        Saiyan warrior1 = new Saiyan();
        Saiyan warrior2 = new Saiyan();

        Saiyan warrior3;
        warrior3 = warrior1;

        boolean bl1 = warrior1.equals(warrior2);
        boolean bl2 = warrior1.equals(warrior3);

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

この流れで何が起きているのか

このプログラムでは、まず new Saiyan() を 2 回行っているので、別々のオブジェクトが 2 つ作られます。

変数指しているもの
warrior11つ目の Saiyan オブジェクト
warrior22つ目の Saiyan オブジェクト

この時点で、warrior1 と warrior2 は別のオブジェクトを指しています。
だから warrior1.equals(warrior2) は false になります。

次に、

warrior3 = warrior1;

と書いています。
これは、新しいオブジェクトを作ったわけではありません。warrior1 が指しているのと同じオブジェクトを、warrior3 も指すようにしただけです。

そのため、

変数指しているもの
warrior11つ目の Saiyan オブジェクト
warrior3warrior1 と同じ Saiyan オブジェクト

という関係になります。

だから warrior1.equals(warrior3) は true になります。

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

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

ここを表で整理すると、こうなります。

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

つまり equals() は最初の状態では、

  • 名前が同じか
  • 戦闘力が同じか
  • 中身が似ているか

を直接見ているわけではありません。

あくまで、同じオブジェクトかどうか を調べるのが基本です。

ドラゴンボールで考える equals() の感覚

ドラゴンボールで感覚的に言いかえると、equals() は
この 2 つの札は、本当に同じ戦士本人を指しているか
を確認するようなものです。

たとえば、

  • warrior1 はベジータを指している
  • warrior3 も同じベジータを指している

なら true です。

でも、

  • warrior1 はベジータを指している
  • warrior2 は別に作られた別の戦士を指している

なら false です。

ここでは「両方ともサイヤ人だから同じ」という判断はしません。
戦士の種類が同じかどうかではなく、実体が同じかどうかを見るのです。

equals() が便利な場面

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

たとえば、

  • 同じオブジェクトを別の名前で管理していないか確認したい
  • 配列やコレクションの中に、すでに同じオブジェクトがあるか見たい
  • 引数で渡されたオブジェクトが、自分が持っているものと同一か確認したい

といった場面で役立ちます。

ドラゴンボール風に言えば、戦士の一覧を扱うときに
「この札は、もう登録済みのあの戦士本人か」
を確かめるような使い方です。

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

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

String クラスでは、equals() は単に「同じオブジェクトを指しているか」を見るのではなく、文字列の内容が同じかどうか を調べるように定義しなおされています。

つまり、2つの文字列オブジェクトが別々のオブジェクトだったとしても、

  • どちらも こんにちは

を表していれば、String クラスの equals() は true を返します。

なぜ String クラスではそうしているのか

文字列では、「同じ実体かどうか」よりも「中身が同じかどうか」のほうが大切になることが多いからです。

たとえばドラゴンボールで技名を表す文字列があったとして、

  • 1つ目のオブジェクトに かめはめ波
  • 2つ目のオブジェクトにも かめはめ波

と入っているなら、別のオブジェクトでも「文字としては同じ」と考えたい場面が多いです。

だから String クラスでは、Object クラスの元の equals() をそのまま使うのではなく、文字列の中身を見るようにオーバーライドしているわけです。

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

この話から見えてくる大事な点は、equals() には 2 つの見方があるということです。

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

今回の中心である Object クラスの equals() では、同じオブジェクトかどうかを調べます。
でも String クラスのように、クラスに合わせて「同じ」の意味を作り直すこともあるのです。

これは、オブジェクト指向らしいとても大事な考え方です。
クラスごとに「比較のしかた」まで自分らしくできる、ということだからです。

図で equals() のしくみを整理する

左上の warrior1 と右上の warrior3 は、どちらも同じ SaiyanオブジェクトA を指しています。
そのため warrior1.equals(warrior3) は true になります。

一方、左下の warrior2 は別の SaiyanオブジェクトB を指しています。
中身が似ていても別のオブジェクトなので、warrior1.equals(warrior2) は false になります。

右下の補足は、String クラスではこの基本のしくみがオーバーライドされていて、文字列の中身で比較することを示しています。

equals() を学ぶときに押さえたい感覚

最後に、感覚としていちばん大事なところを整理しておきます。

Object クラスの equals() は、
この 2 つの変数は、同じ実体を見ているか
を調べるためのものです。

そして、クラスによってはその意味をオーバーライドして、
そのクラスらしい「同じ」の意味
を持たせることもできます。String クラスがその代表例です。

ドラゴンボールで言えば、

  • Object の equals() は「同じ戦士本人かどうか」
  • String の equals() は「技名や文字列の内容が同じかどうか」

を見るような違いです。

この感覚がつかめると、equals() は単なる比較メソッドではなく、オブジェクト指向の中で「同じ」の意味をどう定義するかに関わる、とても大切なメソッドだと見えてきます。