繁体   English   中英

Java集合:列表 <Animal> 老虎=新的ArrayList <Tiger> ()错误

[英]Java Collections: List<Animal> tiger = new ArrayList<Tiger>() WRONG

老虎类是动物类的延伸。

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

但是,我认为这条线适用于多态性。 请谁能为我解释。

你做不到

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家族)

要么:

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

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

List<Animal>将允许您添加一只可爱的小狗。 ArrayList<Tiger>中的哪些老虎随后会吃掉。

多态地说,您将拥有

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

如果需要,您可以依赖并使用接口定义的功能来替换List<Tiger>任何实现。 您要尝试的不是多动症,这只是不安全的转换(尤其是对于上述小狗),并且由于上述原因而无法使用。

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

数组示例

使用数组,您可以执行以下操作:

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

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

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

最后一行可以正常编译,但是如果运行此代码,则可能会得到ArrayStoreException

这意味着您可以欺骗编译器,但不能欺骗运行时类型系统。 之所以这样,是因为数组是我们所谓的可更新类型 这意味着在运行时Java知道此数组实际上是作为整数数组实例化的,而该数组恰好是通过Number[]类型的引用进行访问的。

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

Java泛型的问题

现在,Java泛型类型的问题在于类型信息被编译器丢弃,并且在运行时不可用。 此过程称为类型擦除 在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的设计人员确保您不能欺骗编译器。 如果您不能欺骗编译器(就像我们对数组所做的那样),那么您也不能欺骗运行时类型系统。

因此,我们说泛型是不可更改的

显然,这也会阻碍多形性。 解决方案是学习使用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(所有扩展Number的东西都是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);

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

但是,假设您将得到一个数字,那么您将无法安全地从此反结构中读取任何内容。

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

如您所见,如果编译器允许您编写此行,则在运行时将收到ClassCastException。

获取/放置原理

因此,在仅打算将通用值从结构中取出时,请使用协方差;在仅打算将通用值放入结构中时,请使用逆方差;当您打算同时使用两者时,请使用确切的通用类型。

我最好的例子是将以下任何一种数字从一个列表复制到另一个列表。

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

我同意这令人困惑。 如果允许该类型的语句,这可能会出错:

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!

哦。 是的,你是真的。 您的代码在多态思维中是正确的。 但是,看看我只是个菜鸟时使用了很长时间的代码。 您将了解为什么要感谢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];        
    }

}

如您在上面的代码中所见,当您阻止使用List<Animal> tigerList = new ArrayList<Tiger>();时, Collections是如此“智能” List<Animal> tigerList = new ArrayList<Tiger>();

您应该想象是否有人使用: tigerList.add(a Lion, a Cat,......); --->错误。

因此,总结一下,这是不同的:

数组:在运行时检查。 您会感到更舒适,但危险

集合:在编译时检查。 您会生气,因为它会注意到错误。 但是,您将在运行时防止错误。 !!!!

也许下面的帖子已经解决了您的问题。 但我建议您使用WildCard,例如:

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

是。 您可能会在此行后面看到这个想法。 但是,最有趣的事情是:它将阻止您更改列表。 在这种情况下,请add方法。 例如:

tigerList.add(TIGER); ERROR

是。 这也将阻止您添加老虎:)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM