【Java】Object.equalsメソッドをオーバーライドしたらArrayList.containsメソッドの中でも使えるようになるのか?【@Override】

こんにちは~!
3回目の投稿です!!

今回は、お仕事の中でいただいたJavaについての質問を題材にお話ししたいと思います。

ちなみに私にとって一番歴が長い言語がJavaです!
Oracle社の認定資格(Silver)も持ってます!

質問の内容は、Javaの基本である「オーバーライド」の応用です。

Object型のequalsメソッドをオーバーライドすることで、
ArrayList型のcontainsメソッド内で利用できるようにならないか、という質問でした。

それでは、順を追って解説していこうと思います!

オーバーライドとは?

オーバーライドについて一言で説明すると、

「スーパークラス(親クラス)のメソッドを継承したサブクラス(子クラス)でアレンジして利用する」

ということになります。

こんな感じです↓

public class Main {
    public static void main(String[] args) {
        new ClassSub().foo();
    }
}

// スーパークラス
class ClassSuper {
    public void foo(){
        // 本来の処理
        System.out.println("ClassSuper");
    }
}

// サブクラス
class ClassSub extends ClassSuper{
    @Override
    public void foo(){
        // アレンジしたい処理
        super.foo();
        System.out.println("ClassSub");
    }
}

16行目の「extends ClassSuper」でスーパークラスを継承していますね。

メソッドの定義の前に「@Override」と書いておくことで、
オーバーライドされていない場合(入力ミス等)にエラーで教えてくれます。

「super」を使用することでスーパークラスの本来の処理を呼び出すことが可能なので、
処理を少しだけ変えることもできるし、丸ごと書き換えることもできます。

Object型を継承する場合

本来は「extends」で継承することが必須なのですが、
実はObject型を継承する場合は例外で、「extends Object」と書かなくても継承ができてしまいます!
(これ忘れがちですよね…)

Javaでは「全てのクラスはObject型を暗黙で継承している」ということになっているのです。
言わば全てのクラスのスーパークラスですね。

「extends」を書かなくても、「@Override」を書いてもいいということなのです。

本題:Object.equalsをオーバーライドしてArrayList.containsで使いたい

ここで本題ですが、Object.equalsメソッドをオーバーライドすることで、
それを内部的に利用しているArrayList.containsメソッドで利用できるようになるのでしょうか?

例えば、下記のようなコードを書いてみました↓

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Test> list = new ArrayList<>();
        list.add(new Test());
        System.out.println(list.contains(new Test()));
    }
}

class Test {
    @Override
    public boolean equals(Object o) {
        System.out.println("OK");
        return true;
    }
}
OK
true

おおっ、ちゃんとサブクラスの「System.out.println(“OK”);」が呼び出され、
「OK」が出力されていますね!

では、こちらはどうでしょうか↓

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Test> list = new ArrayList<>();
        list.add(new Test());
        System.out.println(list.contains(new Object()));
    }
}

class Test {
    @Override
    public boolean equals(Object o) {
        System.out.println("OK");
        return true;
    }
}
false

おや、今度はサブクラスのequalsが呼び出されておらず、
本来のequalsが呼び出されていますね。

先程のコードとの違いは、7行目の「list.contains(new Test())」が「list.contains(new Object())」となっていること。

ではでは、今度はこちらはどうでしょうか↓

import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<Test> list = new ArrayList<>();
        list.add(new Test());
        System.out.println(list.get(0).equals(new Object()));
    }
}

class Test {
    @Override
    public boolean equals(Object o) {
        System.out.println("OK");
        return true;
    }
}
OK
true

今度は7行目でcontainsを使わず、「list.get(0).equals(new Object())」としてみましたが、上手くいきましたね!
比較対象がObject型という点では「list.contains(new Object())」と同じはずなのに…

一体どういうことなのでしょうか?

※補足:今回はequalsの戻り値を無条件でtrueとしているので省略していますが、
 equalsをオーバーライドした場合はhashCodeもオーバーライドする必要があります。
 参考…Object (Java Platform SE 8 ) (oracle.com)

ArrayList.containsのリファレンスを見てみよう

困ったときは、リファレンスを探して読んでみましょう!

今回読んだArrayList.containsのリファレンスはこちら

つまり、このリストに、(o==null ? e==null : o.equals(e))となる要素eが1つ以上含まれている場合にのみtrueを返します。

ArrayList (Java Platform SE 8) – Oracle

ここでの「o」がcontainsの引数です。
「e」はリストに格納されている方ですね。

つまり、containsで使われるequalsメソッドはcontainsに指定された引数の型に依存するということが分かりました!

List<Test>と定義していようが、listの中身がTest型だろうが、関係なかったんですね;;

まとめ

以上から、ArrayList.containsの引数に指定されたクラスがObject.equalsをオーバーライドしていた場合は、
ArrayList.containsにて当該サブクラスのequalsが呼び出されることが分かりました!

containsで使われるequalsメソッドはcontainsに指定された引数の型に依存するというところがポイントですね。

皆様の参考になれば幸いです!

それでは、今日はこの辺で。