2011-05-16 55 views
6

我认为我对Java泛型有一些很好的理解。当类是泛型时Java通配符的奇怪行为

This code DOES NOT COMPILE我知道为什么。

我们可以通过测试方法只动物类型的列表或它的超类型(如对象的列表)

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

但是这里来了奇怪的一部分(至少对我来说)。

如果我们只会增加<牛逼>声明类测试作为通用的,那么它编译!并抛出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 

我的问题是,为什么将通用类型<牛逼>(未在任何地方使用)引起的类编译和改变通配符的行为?

回答

7

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

的类型构造函数(§8.8),实例方法(§8.8,第9.4节),或者非静态字段(§8.3)未从其超类或超接口继承的原始类型C的M是在对应于C的泛型声明中删除其类型。原始类型C的静态成员的类型与其在泛型声明中的类型相同对应于C.

List<? super Animal>的消除是List

此定义背后的基本原理可能是原始类型旨在用作从非泛型遗留代码使用泛型类型的手段,其中类型参数从不存在。它们没有设计,并且不是最优的,没有指定类型参数;这就是通配符类型的用途,即如果你的编译器合规性水平大于1.5的代码,你应该写

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

欢喜(或诅咒,因为这件事而定)再次seing编译错误。

+0

+1。我应该补充一点,即使它在Eclipse中编译,它肯定会产生一系列可怕的警告,您应该注意这些警告。 – 2011-05-16 21:42:12

+0

实际上不只是eclipse,该规范强制要求警告:“如果删除更改了方法或构造函数的任何参数的任何类型,则对原始类型的方法或构造函数的调用将生成未经检查的警告。通常,在运行时几乎所有可能导致*堆污染*的代码都需要在编译时生成未经检查的警告。 – meriton 2011-05-16 21:55:10

1

有趣的问题。

我通过为自己编译确认了你的结果,如果你添加了未使用的类型参数,它确实会编译(带有警告)。然而,它未能重新编译,如果你确实指定类型参数类型:

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

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

0

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

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

我的猜测是,由于测试一般是没有定义,编译器决定它不具有足够的信息来检查超出该级别的任何内容

1

您通过添加参数类型:

public class Test<T> { 

但你这样做把它作为原始类型:

new Test() 

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