[英]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.