6

私はJavaジェネリックについてある程度理解していると思いました。

このコードはコンパイルされません。理由はわかっています。

テストメソッドに合格できるのは、動物タイプのリストまたはそのスーパータイプ(オブジェクトのリストなど)のみです。

package scjp.examples.generics.wildcards;

import java.util.ArrayList;
import java.util.List;

class Animal {}
class Mammal extends Animal {}
class Dog extends Mammal {}

public class Test {

    public void test(List<? super Animal> col) {
        col.add(new Animal());
        col.add(new Mammal());
        col.add(new Dog());
    }

    public static void main(String[] args) {
        List<Animal> animalList = new ArrayList<Animal>();
        List<Mammal> mammalList = new ArrayList<Mammal>();
        List<Dog> dogList = new ArrayList<Dog>();

        new Test().test(animalList);
        new Test().test(mammalList); // Error: The method test(List<? super Animal>) in the type Test is not applicable for the arguments (List<Mammal>)  
        new Test().test(dogList);    // Error: The method test(List<? super Animal>) in the type Test is not applicable for the arguments (List<Dog>)

        Dog dog = dogList.get(0);
    }        
}

しかし、ここに奇妙な部分があります(少なくとも私にとっては)。

<T>を追加するだけでクラスTestをジェネリックとして宣言すると、COMPILES!そしてjava.lang.ClassCastExceptionをスローします:

public class Test<T> {
...
}

Exception in thread "main" java.lang.ClassCastException: scjp.examples.generics.wildcards.Animal cannot be cast to scjp.examples.generics.wildcards.Dog

私の質問は、ジェネリッククラスタイプ<T>(どこでも使用されていない)を追加すると、クラスがコンパイルされ、ワイルドカードの動作が変更された理由です。

4

4 に答える 4

7

new Test()はrawタイプです。Java言語仕様では、raw型のメンバーの型を次のように定義しています。

スーパークラスまたはスーパーインターフェースから継承されていない生のタイプCのコンストラクター(§8.8)、インスタンスメソッド(§8.8、§9.4)、または非静的フィールド(§8.3)Mのタイプは、そのタイプの消去です。 Cに対応する総称宣言の型。生の型Cの静的メンバーの型は、Cに対応する総称宣言の型と同じです。

の消去はList<? super Animal>ですList

この定義の背後にある理論的根拠は、生の型が、型パラメーターが存在しない非ジェネリックレガシーコードからのジェネリック型を使用する手段として意図されていることです。それらは設計されておらず、最適とは言えず、タイプパラメータが指定されていません。これがワイルドカードタイプの目的です。つまり、1.5を超えるコンパイラコンプライアンスレベルをコーディングする場合は、次のように記述する必要があります。

    Test<?> test = makeTest();
    test.test(animalList);
    test.test(mammalList);
    test.test(dogList);

そして、コンパイルエラーを再度発生させることを喜んで(または、場合によっては呪いをかけて)ください。

于 2011-05-16T21:39:52.017 に答える
1

興味深い質問です。

自分でコンパイルして結果を確認しましたが、未使用のtypeパラメーターを追加すると、実際にコンパイルされます(警告付き)。ただし、実際にtypeパラメーターにtypeを指定すると、再コンパイルに失敗します。

    new Test<Object>().test(animalList);
    new Test<Object>().test(mammalList);
    new Test<Object>().test(dogList);

私の疑惑は、テストオブジェクトを構築するためにチェックされていない操作を使用しているため、コンパイラは他のパラメータタイプをチェックすることを気にせず、すべてをチェックされていない/安全でないものとして扱うことです。タイプを指定すると、以前の動作に戻ります。

于 2011-05-16T21:39:11.947 に答える
1

以下を追加して、タイプをパラメーター化しました。

public class Test<T> {

しかし、次のようにして、それを生の型として使用します。

new Test()

したがって、すべての賭けは現在オフになっています。レガシーコードとの相互運用性を有効にするために、コンパイラはそれを通過させていますが、現在は型チェックを行っていません。ただし、コンパイラの警告が生成されます。

于 2011-05-16T21:41:06.793 に答える
0

Testクラスに追加<T>してから、ジェネリックに何かを入力すると、コンパイルエラーが返されます

new Test<String>().test(mammalList);

私の推測では、Test genericが定義されていないため、コンパイラーは、そのレベルを超えるものをチェックするのに十分な情報がないと判断します。

于 2011-05-16T21:39:13.137 に答える