今日の役に立たない一言 - Today’s Trifle! -

古い記事ではさまざまなテーマを書いていますが、2007年以降はプログラミング関連の話がほとんどです。

instanceof を使うのは悪いコードか?

instanceof について書いたが、これについて、もう少し個人的な見解を書いておく。
まず、オーバーロードを書いた場合、どのオーバーロードを呼び出すかはコンパイル時に解決される。

    public void method(Object o){
    }
    public void method(A a){
    }
    public void method(B b){
    }

このようなオーバーロードを書いたとする。ただし、

public class B extends A {}

とする。
どのオーバーロードが呼び出されるか、は以下のようになる。

    public void caller(){
        B b = new B();
        Object o = b;
        A a = b;
        // method(Object o) を呼び出す方法
        method(o);
        method((Object)o);
        method((Object)a);
        method((Object)b);
        // method(A a) を呼び出す方法
        method(a);
        method((A)o);
        method((A)a);
        method((A)b);
        // method(B b) を呼び出す方法
        method(b);
        method((B)o);
        method((B)a);
        method((B)b);

つまり、呼び出す側が宣言している型に対応するオーバーロードが呼び出されるのだ。これでは、呼び出し方で振る舞いが変わってしまい、バグの原因となりやすい。
これを回避するには、以下のように、呼び出される側が責任を持って正しいオーバーロードにディスパッチする必要がある。つまり、良いコードを書くためにinstanceof は必要だ。

    public void method(Object o){
        if(o instanceof B){
            method((B)o);
        }else
        if(o instanceof A){
            method((A)o);
        }else{
            // Object クラスのインスタンスに対する処理
        }
    }
    public void method(A a){
    }
    public void method(B b){
    }

instanceof の使い方が良いか悪いかを判断するには、クラスの責任範囲を思い出すと良い。instanceof を記述しようとしているクラスに、見分ける責任があるかどうか。
それと依存関係。依存関係と責任範囲は密接な関係があるので、ある意味冗長かもしれないが、コードから見えるので判断しやすい。insntanceof を書くということは、instanceof演算子の右項に書いたクラスと依存関係ができるということ。依存関係があってよいのか、依存関係をつくらないでおくべきか、というのはひとつの目安となる。
誤解されそうなので、もう少し補足しておく。ここに紹介したサンプルコードは Object を引数とするオーバーロードを作っているが、このコードは必ずしも良いものではない。オーバーロードが処理可能なトップレベルのインターフェースを大元にしてディスパッチするのが良い。そして、ディスパッチを受け持つメソッドだけが instanceof を使い、他のオーバーロードでは使わないようにすることが重要。