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 声明为泛型,那么它会编译!并抛出 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()是原始类型。Java 语言规范定义了原始类型成员的类型,如下所示:

未从其超类或超接口继承的原始类型 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 参数指定了类型,它将无法再次编译:

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

我的怀疑是,因为您使用未经检查的操作来构造 Test 对象,编译器不会费心检查其他参数类型并将整个事情视为未经检查/不安全。当您指定类型时,它会恢复到以前的行为。

于 2011-05-16T21:39:11.947 回答
1

您已经通过添加参数化了类型:

public class Test<T> {

但是然后您通过执行以下操作将其用作原始类型:

new Test()

所以现在所有的赌注都没有了。为了实现与遗留代码的互操作性,编译器允许它通过,但现在不是类型检查。但它会生成一个编译器警告。

于 2011-05-16T21:41:06.793 回答
0

如果您添加<T>到您的测试类,然后用编译错误将返回的东西填充泛型

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

我的猜测是,因为没有定义测试泛型,编译器认为它没有足够的信息来检查超出该级别的任何内容。

于 2011-05-16T21:39:13.137 回答