たなかのJava日記

どんなことをやったか(学んだか)、どこで詰まったか(わからなかったか)、どこで工夫したかの記録です。

【Java】instanceof演算子

【Java】インスタンスの等価判定(前編) - たなかのJava日記
【Java】インスタンスの等価判定(後編) - たなかのJava日記

これまでにインスタンスの等価判定について学びました。
equals()をオーバーライドすることで、何をもって同じ内容かを決めることができました。
今回は、equals()をオーバライドする過程に存在した「instanceof演算子」について学びます。

【instanceof演算子とは】
構文:
変数 instanceof 型名

インスタンスが指定したクラスであるかどうかを判定してくれるキーワードです。
そのクラスで合っているかどうかを判定してくれます。

オペランドが参照型の型名になります。 評価値の型はbooleanです。
オペランド式が、右オペランドの型に代入可能な参照型の時、式の評価値が真(true)になります。
代入可能でない場合は偽になります。 別名「型比較演算子」とも言います。


【何のためにinstanceof演算子が存在するのか】
ダウンキャストを安全に行えるかを事前にチェックするためです。
ダウンキャストとは、あるクラスのサブクラスへと型を変換することです。
もう少し砕いた言い方だと「代入が失敗する可能性」を懸念してエラーを出すコンパイラに対して、
「大丈夫だから黙って代入しろ」と型変換を実行させる命令です。なので、コンパイルは成功します。
しかし、実際に動作させるとClassCastExceptionというエラーが発生する可能性があるのです。
これは、「キャストによる強制代入の結果、ウソだったよ!」と強制停止せざるを得ないという意味のエラーです。

equals()のオーバーライドする過程でも、キャストする場面がありますが、そこで強制停止する可能性があります。
その前に「instanceof演算子」でキャストする前に「キャストして大丈夫かどうか」のチェックを行っているということです。

package jp.co.mtanaka;

import java.util.ArrayList;
import java.util.List;

public class Sample2 {
    public static void main(String[] args) {
        //右辺の型は省略可能
        List<Comedian> list = new ArrayList<>();

        Comedian a = new Comedian();
        a.name = "盛山";
        // Comedian型の変数をListに格納
        list.add(a);
        System.out.println("要素数" + list.size()); //要素数1

        Comedian b = new Comedian();
        b.name = "盛山";
        // 名前が盛山のお笑い芸人を削除
        list.remove(b);
        System.out.println("要素数" + list.size()); //要素数0
    }
}
package jp.co.mtanaka;

public class Comedian {
    public String name;

    // equals()をオーバーライドして、2つのインスタンスの何をもって同一と見なすかを決めて判定する
    @Override
    public boolean equals(Object obj) {
        // 自分自身(同じ参照)が引数として渡されたら無条件でtrueを返す
        if (this == obj) {
            return true;
        }
        // nullが引数で渡されたらfalseを返す
        if (obj == null) {
            return false;
        }
        // obj instanceof Personで左辺は右辺の型と同じかを判断
        // 次の処理でキャストする前に「キャストして大丈夫かどうか」のチェックを行っている
        // !で左辺は右辺と違う型だったらfalseを返す
        if (!(obj instanceof Comedian)) {
            return false;
        }
        // 次の処理に備えて比較ができるようにObject型からキャストする
        Comedian c = (Comedian) obj;

        // 2つのインスタンスのnameが等価(同じ内容)で無ければfalseを返す
        // Stringクラスのequalsメソッドで文字列の値が等しいか判定しています
        if (!this.name.equals(c.name)) {
            return false;
        }
        return true;
    }
}


【まとめ】
Javaの全てのクラスはObject型を親クラスに持つので、Object型としてどんな引数も指定できる。
しかし、引数としてObject型で受けった実体は何のクラスかわからなくなります。
そこでinstanceofで「このクラスじゃないの?」とチェックすれば良い。
このチェックを行わずにキャストを行うと、コンパイルは通るが動作させるとClassCastExceptionというエラーが発生する可能性がある。
そのエラーを防ぐために、ダウンキャストを安全に行えるかを事前にチェックするために、「instanceof演算子」を使用する。
ちなみに、基本データ型はObjectを引数に取るメソッドには使用できません。
これは、基本型はObjectクラスを祖先に持っていないためです。
基本型を引数に取るには値をラッパークラスで包む(変換する)必要があります。

参考サイト:
Javaの型判定でのinstanceof演算子について徹底解説! | Javaコラム
java.lang.Stringクラスのequalsメソッドのソースコードを読んでみた