メソッドはタイプの別のメソッドと同じ消去を持っています


381

同じクラスに次の2つのメソッドを含めることが合法ではないのはなぜですか?

class Test{
   void add(Set<Integer> ii){}
   void add(Set<String> ss){}
}

私は得る compilation error

メソッドadd(Set)には、タイプTestの別のメソッドと同じ消去add(Set)があります。

私はそれを回避することができますが、なぜjavacがこれを好まないのかと思っていました。

多くの場合、これらの2つの方法のロジックは非常によく似ており、1つの方法で置き換えることができます。

public void add(Set<?> set){}

メソッドですが、常にそうであるとは限りません。

constructorsこれらの引数を取る2つが必要な場合は、1つのの名前を変更することはできないため、これは非常に面倒ですconstructors


1
データ構造が不足しても、さらにバージョンが必要な場合はどうなりますか?
Omry Yadan 14

1
基本バージョンから継承するカスタムクラスを作成できます。
willlma 2014年

1
OP、コンストラクターの問題の解決策は思い付きましたか?2種類を受け入れる必要があり、Listその処理方法がわかりません。
トマーシュZato -復活モニカ

9
Javaで作業しているとき、C#が本当に恋しい...
Svish

1
@TomášZato、私はコンストラクタにダミーのパラメータを追加することで解決しました:ブールnoopSignatureOverload。
Nthalk

回答:


357

このルールは、未加工の型をまだ使用しているレガシーコードの競合を回避することを目的としています。

JLSから引用した、これが許可さなかった理由の例を次に示します。ジェネリックがJavaに導入される前に、次のようなコードを書いたとします。

class CollectionConverter {
  List toList(Collection c) {...}
}

あなたはこのように私のクラスを拡張します:

class Overrider extends CollectionConverter{
  List toList(Collection c) {...}
}

ジェネリックの導入後、ライブラリを更新することにしました。

class CollectionConverter {
  <T> List<T> toList(Collection<T> c) {...}
}

更新する準備が整っていないため、Overriderクラスをそのままにします。toList()言語の設計者は、メソッドを正しくオーバーライドするために、生の型がすべてのジェネリック型と「オーバーライド等価」であると判断しました。つまり、メソッドのシグネチャは正式には私のスーパークラスのシグネチャと等しくなくなりましたが、メソッドはオーバーライドされます。

これで時間が経過し、クラスを更新する準備ができたと判断しました。しかし、少し台無しにして、既存のraw toList()メソッドを編集する代わりに、次のような新しいメソッドを追加します。

class Overrider extends CollectionConverter {
  @Override
  List toList(Collection c) {...}
  @Override
  <T> List<T> toList(Collection<T> c) {...}
}

生の型は同等であるため、両方のメソッドはメソッドをオーバーライドする有効な形式になっていますtoList(Collection<T>)。しかし、もちろん、コンパイラーは単一のメソッドを解決する必要があります。このあいまいさをなくすために、クラスはオーバーライドと同等の複数のメソッドを持つことはできません。つまり、消去後に同じパラメーター型を持つ複数のメソッドがあります。

重要なのは、これが生の型を使用して古いコードとの互換性を維持するように設計された言語規則であることです。これは、型パラメーターの消去によって要求される制限ではありません。メソッドの解決はコンパイル時に行われるため、ジェネリック型をメソッド識別子に追加するだけで十分です。


3
すばらしい答えと例!ただし、最後の文を完全に理解しているかどうかはわかりません(「メソッドの解決はコンパイル時に行われるため、消去する前に、この作業を行うために型の具体化は必要ありません。」)。もう少し詳しく説明してもらえますか?
Jonas Eicher

2
理にかなっています。テンプレートメソッドでの型の具体化について少し考えてみましたが、そうです。コンパイラーは、型を消去する前に適切なメソッドが選択されるようにします。綺麗な。レガシーコードの互換性の問題によって汚染されていない場合。
Jonas Eicher

1
@daveloyallいいえ、そのようなオプションを知りませんjavac
エリクソン2014

13
まったくエラーではないJavaエラーに初めて遭遇したのではなく、Javaの作成者だけが他の人と同じように警告を使用した場合にコンパイルされる可能性があります。彼らだけがすべてをよりよく知っていると思っています。
トマーシュZato -復活モニカ

1
@TomášZatoいいえ、そのための明確な回避策は知りません。ダーティなものが必要な場合は、List<?>enum型を示すために定義したものを渡すことができます。タイプ固有のロジックは、実際には列挙型のメソッド内にある可能性があります。または、2つの異なるコンストラクタを持つ1つのクラスではなく、2つの異なるクラスを作成することもできます。一般的なロジックは、両方のタイプが委任するスーパークラスまたはヘルパーオブジェクトにあります。
エリクソン2015

118

Javaジェネリックは型消去を使用します。山かっこ(<Integer>および<String>)内のビットが削除されるため、同じシグネチャを持つ2つのメソッドが作成されます(add(Set)エラーに表示されます)。ランタイムがどちらの場合にどちらを使用するかわからないため、これは許可されません。

Javaが具体化されたジェネリックを取得する場合は、これを行うことができますが、おそらく今はそうではありません。


24
申し訳ありませんが、あなたの回答(および他の回答)では、ここにエラーがある理由を説明していません。オーバーロードの解決はコンパイル時に行われ、コンパイラーはどのメソッドをアドレスでリンクするか、バイトコードで参照されているメソッドが署名ではないと私が信じているものでリンクするかを決定するために必要なタイプ情報を確実に持っています。一部のコンパイラはこれをコンパイルできるとさえ思っています。
スティルガー

5
@Stilgarリフレクションを介して呼び出されたり検査されたりするメソッドを停止するにはどうすればよいですか?Class.getMethods()によって返されるメソッドのリストには2つの同一のメソッドがあり、意味がありません。
Adrian Mouat

5
リフレクション情報には、ジェネリックスを操作するために必要なメタデータを含めることができます。そうでない場合、コンパイル済みのライブラリをインポートするときに、Javaコンパイラはジェネリックメソッドをどのように認識しますか
スティルガー、2011年

4
次に、getMethodメソッドを修正する必要があります。たとえば、ジェネリックオーバーロードを指定して、元のメソッドがジェネリック以外のバージョンのみを返し、ジェネリックとして注釈が付けられたメソッドを返さないようにするオーバーロードを紹介します。もちろん、これはバージョン1.5で行われているはずです。彼らが今それを行うなら、彼らはメソッドの後方互換性を壊すでしょう。型の消去はこの動作を指示するものではないという私の発言を支持します。リソースが限られているためか、十分な作業が得られなかったのは実装です。
スティルガー、2011年

1
これは正確な答えではありませんが、便利なフィクションで問題をすばやく要約します。メソッドのシグネチャが類似しすぎているため、コンパイラが違いを知らせない可能性があり、「未解決のコンパイルの問題」が発生します。
2013年

46

これは、Java GenericsがType Erasureで実装されているためです。

メソッドは、コンパイル時に次のようなものに変換されます。

メソッドの解決はコンパイル時に行われ、型パラメーターは考慮されません。(エリクソンの答えを見てください

void add(Set ii);
void add(Set ss);

両方のメソッドは、型パラメーターなしで同じシグニチャーを持っているため、エラーになります。


21

問題は、ということですSet<Integer>し、Set<String>実際として扱われるSetJVMから。セットのタイプ(文字列または整数)を選択することは、コンパイラーが使用する構文糖衣だけです。JVMは区別することはできませんSet<String>Set<Integer>


7
JVM ランタイムにはそれぞれを区別するための情報がないことは事実ですSetが、メソッドの解決はコンパイル時に行われるため、必要な情報が利用可能な場合、これは関係ありません。問題は、これらのオーバーロードを許可するとraw型の許容値と競合するため、Java構文では違法とされていたことです。
エリクソン

@ericksonコンパイラーが呼び出すメソッドを知っている場合でも、バイトコードの場合とは異なり、どちらもまったく同じに見えます。(Ljava/util/Collection;)Ljava/util/List;機能しないため、メソッド呼び出しの指定方法を変更する必要があります。を使用することもできますが (Ljava/util/Collection<String>;)Ljava/util/List<String>;、これは互換性のない変更であり、消去されたタイプしかない場所では解決できない問題が発生します。おそらく完全に消去を落とさなければならないでしょうが、それはかなり複雑です。
maaartinus

@maaartinusはい、メソッド指定子を変更する必要があることに同意します。私は彼らがその試みをあきらめる原因となった解決できない問題のいくつかに対処しようとしています。
エリクソン2018

7

のようなタイプなしで単一のメソッドを定義します void add(Set ii){}

選択に基づいてメソッドを呼び出すときに、タイプを指定できます。どのタイプのセットでも機能します。


3

コンパイラは、JavaバイトコードでSet(Integer)をSet(Object)に変換する可能性があります。この場合、Set(Integer)は、構文チェックのためのコンパイル段階でのみ使用されます。


5
技術的にはrawタイプSetです。ジェネリックスはバイトコードには存在せず、キャストするための構文上の砂糖であり、コンパイル時の型安全性を提供します。
Andrzej Doyle

1

私はこのようなものを書こうとしたときにこれにぶつかりました: Continuable<T> callAsync(Callable<T> code) {....} そして Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...} それらはコンパイラにとって2つの定義になります Continuable<> callAsync(Callable<> veryAsyncCode) {...}

型消去とは、文字通り、ジェネリックから型引数情報を消去することを意味します。これは非常に迷惑ですが、これはしばらくの間Javaに伴う制限です。コンストラクタの場合、あまり実行できない場合があります。たとえば、コンストラクタの異なるパラメータに特化した2つの新しいサブクラス。または、代わりに初期化メソッドを使用してください...(仮想コンストラクタ?)異なる名前で...

同様の操作方法の場合、名前を変更すると、

class Test{
   void addIntegers(Set<Integer> ii){}
   void addStrings(Set<String> ss){}
}

またはいくつかのより説明的な名前で、オユ例のためのような自己文書化addNamesし、addIndexesまたはかかります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.