型パラメータの扱いの困ったところ。

Genericsの型パラメータはコンパイラへの情報伝達に過ぎないらしく、実行時に「パラメータとして何のクラスが渡されたか」の情報は持っていないご様子。そのせいか何かわからんが、とにかくオーバーロード*1時にちょっと困ったことになる。

元ネタはServiceのAPIをどげんかせんといかん - うなの日記。いつもお世話になってます。

こう使いたい!

違うクラスのID同士を比較した場合、「違う」と通知してほしいので、↓のようなテストケースを書いた。

    ID<Hoge> hogeID = new ID<Hoge>(123);
    ID<Fuga> fugaID = new ID<Fuga>(123);
    assertFalse(hogeID.equals(fugaID));

で、対応するIDクラスの中で↓のように書いた。

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof ID) {
            ID<E> that = (ID<E>) obj;       //warning
            return this.id.equals(that.id);
        }
        return false;
    }

が、テスト不合格。objからthatにキャストする部分で型パラメータは無視してしまうらしい。かといって(obj instanceof ID)と書こうとしてもコンパイルエラーになる。

悩んだ挙句、↓にした。

    @Override
    public boolean equals(Object obj) {
        return false;
    }
    
    public boolean equals(ID<E> that){
        if(that!=null) return this.id.equals(that.id);
        return false;
    }

これで、hogeID.equals(fugaID)からhogeID.equals(Object)が呼ばれるようになり、意図通りの動作になった。

めでたしめでたし。だが…。

気になったので試したが、↓はエラー

    @Override
    public boolean equals(Object obj) {
        return false;
    }
    
    public boolean equals(ID<E> that){
        if(that!=null) return this.id.equals(that.id);
        return false;
    }
    
    public boolean equals(ID<String> that){
        return false;
    }

実行時には型パラメータの違いでクラスは区別されないため、エラーとなる。こりゃ何か気持ち悪い。

*1:この単語を使うのは初めてだw