当类是通用的时,Java通配符奇怪的行为

时间:2011-05-16 21:25:53

标签: java generics wildcard scjp

我认为我对Java泛型有了一些很好的理解。

此代码无法编译,我知道原因。

我们只能传递给测试方法Animal类型的List或它的超类型(如对象列表)

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);
    }        
}

但是这里有一个奇怪的部分(至少对我而言)。

如果我们通过仅添加&lt; T&gt;声明类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

我的问题是为什么要添加通用类类型&lt; T&gt; (它没有在任何地方使用)导致类编译和更改通配符行为?

4 个答案:

答案 0 :(得分:7)

表达式new Test()是原始类型。 Java语言规范defines原始类型的成员类型如下:

  

未从其超类或超接口继承的原始类型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);

并且在再次发现编译错误时欢喜(或诅咒,就此而言)。

答案 1 :(得分:1)

有趣的问题。

我通过自己编译确认了你的结果,如果你添加了未使用的类型参数,它确实会编译(带警告)。但是,如果您实际为type参数指定了类型,则无法再次编译:

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

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

答案 2 :(得分:1)

您已通过添加以下参数化类型:

public class Test<T> {

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

new Test()

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

答案 3 :(得分:0)

如果将<T>添加到Test类中,然后使用某些内容填充泛型,则会返回编译错误

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

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