アプリ版:「スタンプのみでお礼する」機能のリリースについて

以前にも似たような質問をしたことがありますが、それに関しての質問です。

次のようなプログラムを書きました。
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が同じではないので、オーバーライド等価ではない、従ってオーバーライドできないと思ったのですが、どうしてオーバーライドできるのでしょうか。

A 回答 (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 を持っているのでオーバーライドになるけど、コーディング上はオーバーロードなので矛盾が生じます

みたいな感じでしょうか。
質問の意図を汲み違えてたらごめんなさい。
    • good
    • 0
この回答へのお礼

なるほど!
ご回答ありがとうございます。
まさに質問の意図の通りです。

つまり、Aクラスのdisplay(T)は、コンパイルの時にはTはStringクラスとして扱われるため,Testクラスのdisplay(Object)はAクラスのdisplay(T)をオーバーロードしている。しかし、実行時にはそれぞれのdisplayメソッドのシグネチャが一致してしまうためオーバーライド扱いになってしまう。
という矛盾が生じているのですね。

ここの部分がまさに分からなかったので、疑問がはれました!

前回に続き、ご回答ありがとうございました。

お礼日時:2009/09/10 11:34

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>はジェネリクスとして無意味だ。

「なぜ、フィールドやメソッドだけでなく、クラスにもジェネリクスを指定するのか」を考えれば、自然と理解できるんじゃないかと思う。
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。

Testクラスのdisplay(Object)がなぜ、Aクラスのdisplay(T)をオーバーライドできないのかという疑問がはれました。

コンパイル時にはTはString型として認識されていますが、実行時にはerasureに基づいてObject型に置き換えられるためオーバーライドとオーバーロードの矛盾が生じるということだったのですね。

今後も「クラスにジェネリックスを指定する」意味を考えながらジェネリックスを勉強していきます!

お礼日時:2009/09/10 11:24

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!