13

私が次のものを持っているとしましょう:

public <T extends Widget> List<T> first(T n) {
    return first(n.getClass());
}
public <T extends Widget> List<T> first(Class<T> n) {
    return new ArrayList<>();
}

コンパイラは 3 行目で " incompatible types; required: java.util.List<T>; found: java.util.List<capture#1 of ? extends my.app.Widget>" でエラーを出します。理由がわかりません。Tサブタイプ以外のいずれの場合でも、タイプが決して変更されないことは私には理にかなっているように思えます。

これは、明示的なキャストによって修正できますが、なぜ必要なのかはわかりません。

public <T extends Widget> List<T> first(T n) {
    return (List<T>)first(n.getClass());
}
public <T extends Widget> List<T> first(Class<T> n) {
    return new ArrayList<>();
}

これはコンパイラのバグでしょうか?

JDK 1.7.0_15 を使用していることに注意してください。

java version "1.7.0_15"
Java(TM) SE Runtime Environment (build 1.7.0_15-b03)
Java HotSpot(TM) 64-Bit Server VM (build 23.7-b01, mixed mode)
4

4 に答える 4

7

あなたが述べたまさにその理由のために、typeパラメータは実際にはあなたが渡したオブジェクトの実際のランタイムタイプのスーパータイプである可能性があります! T#getClass()戻りませんClass<T>、戻りますClass<? extends T>

Number number = Integer.valueOf(1);
List<Number> list = first(number);

実行時に呼び出すと、ではなくn.getClass()が返されますが、結果を!に割り当てようとしています。コンパイラーは、実際のランタイム・タイプが何であるかを知る方法がなく、せいぜい戻ってくるものしか知りません。キャストを強制することは、「この操作の安全性を保証することはできません。それが正しいことはあなたの言葉にあります」という言い方です。Integer.classNumber.classList<Number>List<? extends Number>

コンパイラーが操作がタイプセーフであることを確認できない場合はいつでも、キャストを強制するため、「チェックされていない」警告が発生し、問題について通知する役割を果たします。

于 2013-03-07T07:31:59.467 に答える
4

getClass()type の値を返しますClass<?>。の他のオーバーライドに渡すと、 へのfirst未チェックのキャストが実行されClass<? extends Widget>ます。ジェネリックは実行時に「型消去」を使用するため、チェックされません。つまり、JVM はそれをチェックできません。-Xlint:uncheckedこの警告を表示するには、パスしてください。

これは ではないことに注意してくださいClass<T extends Widget>。つまり、型システムは、このメソッドの型パラメーター ( の最初のオーバーライドfirst) が、呼び出されるメソッド (他の ) の型パラメーターと同じであることを保証しませんfirst

したがって、返される結果は型List<? extends Widget>(ここで? extends Widgetcapture#1) であり、 と互換性がないList<T extends Widget>ため、コンパイラは正しくエラーを生成します。

この場合、(コンパイラーは知りませんが) これが合理的なことであることをたまたま知っているので、明示的なキャストでオーバーライドすることができます。しかし、その場合、メソッドを次のようにしないのはなぜですか。

public <T extends Widget> List<T> first() {
    return new ArrayList<T>();
}
于 2013-03-07T07:30:00.273 に答える
3

これは、コンパイラがこれを直接解決できないため、常にではなくn.getClass()常に戻るためです。明示的にキャストするには、常にここで行う必要があります。例えばClass<?>Class<T>

public <T extends Widget> List<T> first(T n) {
    return first((Class<T>)n.getClass());
}
于 2013-03-07T07:35:44.107 に答える
1

ジェネリックスとそれぞれの最終的なタイプは、実行時ではなく、コンパイル時に決定されます。実行時に、すべての一般的な情報が消去されます。これは型消去と呼ばれます。ここでは、関数呼び出し時にジェネリック型を判別しようとしているようですが、これはJavaではサポートされていません。

public <T extends Widget> List<T> first(T n) {
    return first(n.getClass());
}

List<T>ここでは、リストのインスタンスを返す必要があります。具体的なインスタンスには、具体的なパラメータが必要Tです。つまり、戻ることはできませんList<T extends Widget>-タイプは不明です。そのようなリストに何を保存しますか?

public <T extends Widget> List<T> first(Class<T> n) {
    return new ArrayList<>();
}

ここでは、提供されたクラスに基づいてリストを返したいと思いますが、実際にはジェネリックArrayListを作成していて、それが必要な型にキャストされていることを確認してください。問題は、ここでのTが前の方法のTと同じではないことです。それらを同じにするために、1つのTでジェネリッククラス全体を宣言する必要があります。

于 2013-03-07T07:31:41.103 に答える