erasureの働きがよく分からない

java.lang.Class(およびそのスーパーインタフェースjava.lang.reflect.AnnotatedElement)のメソッドgetAnnotation()は次のようなシグネチャをしている

public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
...

clazzの型がClass< ?>(総称型)の場合、

Table annotaion = clazz.getAnnotation(Table.class);

はエラーにならないが、clazzの型がClass(raw型)場合、上の書き方だと「型の不一致」でエラーになる。

clazzの型がClass(raw型)の場合、キャストして

Table annotaion = (Table) clazz.getAnnotation(Table.class);

とするか、メソッドの型パラメータにクラスを指定して

Table annotaion = clazz.<Table>getAnnotation(Table.class);

のように書くとエラーにならない。


オブジェクトがraw型だとその総称メソッドの型推論がうまくいかないのは、どうやらerasureによるものらしい。(cf: JLS "4.8 Raw Types")
問題を整理する為に単純化したサンプルを用意した。

パターン1 総称型

public final class RawMembers<X> {
    public <S> S noArg() {
        return null;
    }
    public <T> T withArg(T arg) {
        return null;
    }
    public static void main(String[] args){
        RawMembers<?> rw = new RawMembers();
        String ret1 = rw.<String>withArg("");  // 警告無し
        String ret2 = rw.withArg("");          // 警告無し
        String ret3 = rw.<String>noArg();      // 警告無し
        String ret4 = rw.noArg();              // 警告無し
    }
}

rwが総称型(RawMembers< ?>)であるため問題なくコンパイルできる。
最後のは代入先のret4がStringである為、自動でString型の戻り値と推論されるようだ。

パターン2 raw型

public final class RawMembers<X> {
    public <S> S noArg() {
        return null;
    }
    public <T> T withArg(T arg) {
        return null;
    }
    public static void main(String[] args){
        RawMembers rw = new RawMembers(); 
        String ret1 = rw.<String>withArg("");  // エラー! 現在総称ではありません
        String ret2 = rw.withArg("");          // 警告! パラメータ化される必要があります & エラー!型の不一致
        String ret3 = rw.<String>noArg();      // エラー! 現在総称ではありません
        String ret4 = rw.noArg();              // エラー! 型の不一致
    }

}

rwがraw型な為、noArg()もwithArg()も戻り値の型はerasure(Object型)になる。(仕様のようだ。)
型を指定しようにも「現在総称ではありません」と言われてエラーになる。

パターン3 raw型だけどNonGenericな親クラスから継承(Overrideなし)

public class NonGeneric {
    <S> S noArg() {
        return null;
    }
    <T> T withArg(T arg) {
        return null;
    }
}

public final class RawMembers<X> extends NonGeneric {
    public static void main(String[] args){
        RawMembers rw = new RawMembers(); 
        String ret1 = rw.<String>withArg("");  // 警告無し
        String ret2 = rw.withArg("");          // 警告無し
        String ret3 = rw.<String>noArg();      // 警告無し
        String ret4 = rw.noArg();              // 警告無し
    }
}

NonGenericのerasureはNonGenericなので、問題なくコンパイルできる。(これもここにある通り)

パターン4 raw型だけどNonGenericなインタフェースを実装

public interface NonGeneric {
    <S> S noArg();
    <T> T withArg(T arg);
}

public final class RawMembers<X> implements NonGeneric {
    public <S> S noArg() {
        return null;
    }
    public <T> T withArg(T arg) {
        return null;
    }
    public static void main(String[] args){
        RawMembers rw = new RawMembers(); 
        String ret1 = rw.<String>withArg("");  // 警告無し
        String ret2 = rw.withArg("");          // 警告! パラメータ化される必要があります & エラー!型の不一致
        String ret3 = rw.<String>noArg();      // エラー! 現在総称ではありません
        String ret4 = rw.noArg();              // エラー! 型の不一致
    }
}

rwがraw型な為、noArg()もwithArg()も戻り値の型はerasure(Object型)になる。
引数有りのメソッドについては、なぜか改めて型を指定してやることで総称メソッドとして使用できる。
引数なしのメソッドについては、現在総称ではありませんと言われ型を指定できない。

疑問

  • S,TはXと無関係なのになぜ消去されるのか
    • まあ仕様だからか……
  • 非総称型のスーパークラス/インタフェースにあるメソッドをオーバーライドしていて、かつ型パラメータを引数でも使用している場合(パターン4のret1の行の例)だけ、メソッドの型を指定可能なのはなぜか
    • ???