- ベストアンサー
java ジェネリックスに関して
以前にも似たような質問をしたことがありますが、それに関しての質問です。 次のようなプログラムを書きました。 class A<T> { public void display(T t) { System.out.println("A class"); } } public class Test extends A<String> { public void display(Object t) { //問題の行 System.out.println("Test class"); } public static void main(String[] args) { } } 上記の問題の行のところでエラーが出ました。 名前の競合: 型 Test のメソッド display(Object) は型 A<T> の display(T) と同じ erasure を持っていますが、オーバーライドしません A<T>のメソッドdisplay(T)のerasureはdisplay(Object)になるので、display(T)は確かにTestのメソッドdisplay(Object)と同じerasureを持っています。しかしそうなると、Testのdisplay(Object)のシグネチャがA<T>のメソッドdisplay(T)のシグネチャのerasureと同じになるため、オーバーライドできることになると考えたのですが、コンパイル結果はエラーとなってしまいました。 どうしてオーバーライドできないのでしょうか。 例えば public class Test extends A<String> を public class Test<T> extends A<T> にかえた場合はうまく行きました。従ってTをStringと指定しているところに問題があると思うのですが、どうしてコンパイルできないのでしょうか。 また、Testのdisplay(Object)をdisplay(String)にかえた場合(このとき、他の部分ははじめのプログラムと同じ)、A<T>クラスのdisplay(T)をオーバーライドできました。今度はTestクラスのdisplay(String)とA<T>クラスのdisplay(T)はerasureが同じではないので、オーバーライド等価ではない、従ってオーバーライドできないと思ったのですが、どうしてオーバーライドできるのでしょうか。
- みんなの回答 (2)
- 専門家の回答
質問者が選んだベストアンサー
継承時にパラメータ型を指定しているからです。 Testクラスで継承するAクラスのパラメータ型にStringを指定したので、 Testクラス内ではAクラスのT型情報は全てString型として扱われます。 コンパイル後は確かに型情報は失われるためObject型ではありますが、 コンパイル前にStringの型チェックが行われるため実質String型です。 つまり、Testクラスの親のAクラスのdisplayメソッドの引数は String型。 そのためTestクラスのdisplayメソッドはオーバーロード扱い。 でも実際にコンパイルされるとAクラスのdisplayメソッドの引数はObject型になるので Testクラスのdisplayメソッドはオーバーライド扱いになる。 という矛盾が生じます。 名前の競合: 型 Test のメソッド display(Object) は型 A<T> の display(T) と同じ erasure を持っていますが、オーバーライドしません のエラーを適当な感じに要約すると 名前の競合: 型 Test のメソッド display(Object) は型 A<T> の display(T) と同じ erasure を持っているのでオーバーライドになるけど、コーディング上はオーバーロードなので矛盾が生じます みたいな感じでしょうか。 質問の意図を汲み違えてたらごめんなさい。
その他の回答 (1)
public class Test extends A<String> { public void display(String t) { これなら、問題はない。あるいは、 public class Sample extends A<Object> { public void display(String t) { とかでもOKだ。A<T>を、Test extends A<String>として継承しているわけだから、<T>は、<String>でなければならない。<String>をスーパークラスの<Object>に置き換えることはできない。だからdisplay(T t)はdisplay(Object t)にはできない。 逆に、「extends A<Object>」として、display(String t)はできる。「TはObject」だが、StringはObjectとして扱うことができるから問題はない。 おそらく、A<T>のTと、display(T t)のTは、どちらも同じクラスを示すものである、ということを忘れているんじゃないだろうか。何のために、(実際にそれが利用されているメソッドやフィールドなどではなく)クラスに<T>が指定されるかといえば、それは「このクラスで、<T>という(現時点では不特定だが、実行時には特定のクラスに決定される)クラスが使われる」ということを示すため。その「不特定なクラス」を実際に使用しているのがdisplayのTになる。 つまり、このdisplayのTは、class A<T>で指定したTが実際に使われているところ、というわけになる。だから、両者は同じクラス指定となるのが当然だし、クラスで指定された<T>とあえて異なるクラスを指定するのであれば、そもそも「クラスの<T>の指定が正しくされていない」ということになる。そもそもジェネリクスは「特定のクラスのみを受け付ける」ようにするためのものなわけだから、<Object>はジェネリクスとして無意味だ。 「なぜ、フィールドやメソッドだけでなく、クラスにもジェネリクスを指定するのか」を考えれば、自然と理解できるんじゃないかと思う。
お礼
ご回答ありがとうございます。 Testクラスのdisplay(Object)がなぜ、Aクラスのdisplay(T)をオーバーライドできないのかという疑問がはれました。 コンパイル時にはTはString型として認識されていますが、実行時にはerasureに基づいてObject型に置き換えられるためオーバーライドとオーバーロードの矛盾が生じるということだったのですね。 今後も「クラスにジェネリックスを指定する」意味を考えながらジェネリックスを勉強していきます!
お礼
なるほど! ご回答ありがとうございます。 まさに質問の意図の通りです。 つまり、Aクラスのdisplay(T)は、コンパイルの時にはTはStringクラスとして扱われるため,Testクラスのdisplay(Object)はAクラスのdisplay(T)をオーバーロードしている。しかし、実行時にはそれぞれのdisplayメソッドのシグネチャが一致してしまうためオーバーライド扱いになってしまう。 という矛盾が生じているのですね。 ここの部分がまさに分からなかったので、疑問がはれました! 前回に続き、ご回答ありがとうございました。