Java Collections:List <animal> tiger = new ArrayList <tiger>()WRONG </tiger> </animal>

时间:2012-03-28 04:12:35

标签: java generics collections

Tiger类是从Animal Class扩展而来。

当我宣布:List<Animal> tiger = new ArrayList<Tiger>();时。我会在编译时出错。

但是,我认为这条线对多态性来说是正确的。谁能为我解释一下。

5 个答案:

答案 0 :(得分:5)

你做不到

List<Animal> tiger = new ArrayList<Tiger>();

在java中。左边的通用类型必须非常平等(或者可能不必相等,如果通配符在游戏中 - ? extends T? super T)到右边的泛型类型。

如果有可能那么就不可能将新的Lion添加到声明为Animal列表的列表中 - 这是没有意义的。

你能做的是:

List<Animal> tigers = new ArrayList<Animal>();
tigers.add(new Tiger());

Animal的所有系列,包括Tiger s)

或:

List<? extends Animal> tigers = new ArrayList<Tiger>();
tigers.add(new Tiger()); // Adding is immpossible now - list can be read only now! 

(只有Animal的子类) - 列表现在只能读取!

答案 1 :(得分:4)

List<Animal>允许你添加一只可爱的小狗。然后ArrayList<Tiger>中的老虎会吃什么。

多态地说,你会有

List<Tiger> tigers = new ArrayList<Tiger>();

如果您愿意,可以替换使用List<Tiger>的任何实现,依赖并使用界面定义的功能。你要做的不是多态性,它只是一种不安全的转换(特别是前面提到的小狗),并且由于上述原因而无法工作。

答案 2 :(得分:2)

其原因基于Java如何实现泛型。我发现解释它的最好方法是首先使用数组。

数组示例

使用数组,您可以这样做:

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

但是,如果你试图这样做会发生什么?

Number[0] = 3.14; //attempt of heap pollution

这最后一行编译得很好,但是如果你运行这段代码,你可以获得ArrayStoreException

这意味着你可以欺骗编译器,但你不能欺骗运行时类型系统。这是因为数组就是我们所说的可再生类型。这意味着在运行时Java知道这个数组实际上是实例化为一个整数数组,它恰好通过Number[]类型的引用访问。

因此,正如您所看到的,有一件事是对象的真实类型,另一件事是您用来访问它的引用类型,对吗?

Java泛型问题

现在,Java泛型类型的问题是编译器丢弃了类型信息,并且在运行时它不可用。此过程称为type erasure。有很好的理由在Java中实现这样的泛型,但这是一个很长的故事,它与二进制兼容现有的代码有关。

但重要的是,由于在运行时没有类型信息,因此无法确保我们不会造成堆污染。

例如,

List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);

List<Number> myNums = myInts;
myNums.add(3.14); //heap polution

如果Java编译器没有阻止你在编译时这样做,那么运行时类型系统也不能阻止你,因为在运行时没有办法确定这个列表应该只是一个整数列表。 Java运行时允许你将任何你想要的东西放入这个列表,当它只包含整数时,因为它在创建时被声明为整数列表。

因此,Java的设计者确保你不能欺骗编译器。如果你不能欺骗编译器(我们可以用数组做),你也不能欺骗运行时类型系统。

因此,我们说泛型类型不可恢复

显然,这也会妨碍pollymorphism。解决方案是学习使用Java泛型的两个强大功能,称为协方差和逆变。

<强>协方差

使用协方差,您可以从结构中读取项目,但不能在其中写入任何内容。所有这些都是有效的声明。

List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>()
List<? extends Number> myNums = new ArrayList<Double>()

您可以阅读myNums

Number n = myNums.get(0);

因为您可以确定无论实际列表包含什么,它都可以被上传到一个数字(毕竟任何扩展Number的数字都是数字,对吗?)

但是,不允许将任何内容放入协变结构中。

myNumst.add(45L);

这是不允许的,因为Java无法保证真实对象的实际类型是什么。它可以是扩展Number的任何东西,但编译器无法确定。所以你可以阅读,但不能写。

<强>逆变

有了逆转,你可以做相反的事情。你可以把东西放到一个通用的结构中,但你不能从中读出来。

List<Object> myObjs = new List<Object();
myObjs.add("Luke");
myObjs.add("Obi-wan");

List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);

在这种情况下,对象的实际性质是对象列表,并且通过逆变,您可以将Numbers放入其中,主要是因为数字将Object作为共同的祖先。因此,所有Numbers都是对象,因此这是有效的。

然而,假设您将得到一个数字,您无法安全地从这个逆变结构中读取任何内容。

Number myNum = myNums.get(0); //compiler-error

如您所见,如果编译器允许您编写此行,则会在运行时获得ClassCastException。

获取/放置原则

因此,当您只打算从结构中取出通用值时使用协方差,当您只打算将通用值放入结构时使用逆变,并在打算同时使用完全通用类型时使用。[/ p >

我所拥有的最好的例子是将任何类型的数字从一个列表复制到另一个列表中。

public static void copy(List<? extends Number> source, List<? super Number> destiny) {
    for(Number number : source) {
        destiny.add(number);
    }
}

由于协方差和逆变的力量,这适用于这样的情况:

List<Integer> myInts = asList(1,2,3,4);
List<Integer> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();

copy(myInts, myObjs);
copy(myDoubles, myObjs);

答案 3 :(得分:1)

我同意这令人困惑。如果允许这种类型的陈述,这可能会出错:

List<Tiger> tigers = new ArrayList<Tiger>();  // This is allowed.
List<Animal> animals = tigers;  // This isn't allowed.
tigers.add(new Lion());  // This puts a Lion in tigers!

答案 4 :(得分:1)

喔。是的,你是的。你的代码在多态性思维中是正确的。但是,看看我使用的代码很长一段时间,当我只是一个新手。 你会明白为什么要感谢Collection

class Animal{   
}
class Tiger extends Animal{

}
public class Test {

    public static void main (String[] args){

        List<Animal> animal = new ArrayList<Animal>();  //obvious
        List<Tiger> tiger = new ArrayList<Tiger>();     //obvious

        List<Animal> tigerList = new ArrayList<Tiger>();  //error at COMPILE-TIME
        Animal[] tigerArray = new Tiger[2];     //like above but no error but....

        Animal tmpAnimal = new Animal();
        /*
         * will meet RUN-TIME error at below line when use Array
         * but Collections can prevent this before at COMPILE-TIME
         */
        tigerArray[0] = tmpAnimal;   //Oh NOOOO. RUN-TIME EXCEPTION

        /*
         * Below examples WRONG for both Collection and Array
         * Because here is Polymorphism problem. I just want to make more clearer
         */
        List<Tiger> animalList = new ArrayList<Animal>();
        Tiger[] animalArray = new Animal[2];        
    }

}

当您看到我的上述代码时,Collections在阻止您使用List<Animal> tigerList = new ArrayList<Tiger>();时非常“智能”

你应该想象一下如果有人使用:tigerList.add(a Lion, a Cat,......); ---&gt; ERROR。

所以,对于Sumarize来说,这是不同的:

  

ARRAY:在RUN-TIME检查。你会觉得更舒服,但危险

     

收集:在COMPILE-TIME检查。你会因为它而生气   注意错误。但是,您将在运行时防止错误。 !!!!

也许以下帖子已经结束了你的问题。但我建议您使用WildCard:

List<? extends Animal> tigerList = new ArrayList<Tiger>();

是。你可能会看到这条线背后的想法。但是,最有趣的事情是:它会阻止你改变列表。在这种情况下,add方法。 例如:

tigerList.add(TIGER); ERROR

是肯定的。它也会阻止你添加一只老虎:)