简体   繁体   English

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

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

Tiger class is extends from Animal Class. 老虎类是动物类的延伸。

When I declare: List<Animal> tiger = new ArrayList<Tiger>(); 当我声明时: List<Animal> tiger = new ArrayList<Tiger>(); . I will error at compile-time. 我会在编译时出错。

But, I think this line is true for polymorphism. 但是,我认为这条线适用于多态性。 Who can explain for me, please. 请谁能为我解释。

you cannot do 你做不到

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

that in java. 在java中。 Generic type on left have to be exacly equal (or may not have to be equal, if wild cards are in game - ? extends T or ? super T ) to generic type on right. 左边的通用类型必须与右边的通用类型完全相等(或者,如果通配符在游戏中,则可能不必相等- ? extends T? super T )。

If it was possible then it would be impossible to add new Lion to list declared as list of Animal s - that would make no sense. 如果有可能,则不可能将新的Lion添加到声明为Animal列表的列表中-这没有任何意义。

What you can do is: 您可以做的是:

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

(all family of Animal s, including Tiger s) (所有Animal家族,包括Tiger家族)

or: 要么:

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

(only subclasses of Animal ) - list can be read only now! (仅Animal子类) -列表现在只能读取!

A List<Animal> would allow you to add a cute little puppy. List<Animal>将允许您添加一只可爱的小狗。 Which the tigers in the ArrayList<Tiger> would then eat. ArrayList<Tiger>中的哪些老虎随后会吃掉。

Polymorphically speaking, you would have 多态地说,您将拥有

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

Which would allow you to replace use any implementation of List<Tiger> if you so desired, relying upon and using the functionality as defined by the interface. 如果需要,您可以依赖并使用接口定义的功能来替换List<Tiger>任何实现。 What you are trying to do isn't polymorhpism, it is simply an unsafe conversion (particularly for the aforementioned puppy) and is not going to work for reasons illustrated above. 您要尝试的不是多动症,这只是不安全的转换(尤其是对于上述小狗),并且由于上述原因而无法使用。

The reasons for this are based on how Java implements generics. 原因是基于Java如何实现泛型。 The best way I have found to explain it is by using arrays first. 我发现最好的解释方法是首先使用数组。

An Arrays Example 数组示例

With arrays you can do this: 使用数组,您可以执行以下操作:

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

But, what would happen if you try to do this? 但是,如果尝试这样做会发生什么?

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

This last line would compile just fine, but if you run this code, you could get an ArrayStoreException . 最后一行可以正常编译,但是如果运行此代码,则可能会得到ArrayStoreException

This means that you can fool the compiler, but you cannot fool the runtime type system. 这意味着您可以欺骗编译器,但不能欺骗运行时类型系统。 And this is so because arrays are what we call reifiable types . 之所以这样,是因为数组是我们所谓的可更新类型 This means that at runtime Java knows that this array was actually instantiated as an array of integers which simply happens to be accessed through a reference of type Number[] . 这意味着在运行时Java知道此数组实际上是作为整数数组实例化的,而该数组恰好是通过Number[]类型的引用进行访问的。

So, as you can see, one thing is the real type of the object, an another thing is the type of the reference that you use to access it, right? 因此,正如您所看到的,一件事是对象的实际类型,另一件事是用于访问对象的引用的类型,对吗?

The Problem with Java Generics Java泛型的问题

Now, the problem with Java generic types is that the type information is discarded by the compiler and it is not available at run time. 现在,Java泛型类型的问题在于类型信息被编译器丢弃,并且在运行时不可用。 This process is called type erasure . 此过程称为类型擦除 There are good reason for implementing generics like this in Java, but that's a long story, and it has to do with binary compatibility with pre-existing code. 在Java中实现这样的泛型是有充分的理由的,但这是一个很长的故事,它与预先存在的代码的二进制兼容性有关。

But the important point here is that since, at runtime there is no type information, there is no way to ensure that we are no committing heap pollution. 但是这里的重点是,由于在运行时没有类型信息,因此无法确保我们不会造成堆污染。

For instance, 例如,

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

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

If the Java compiler does not stop you from doing this at compile time, the runtime type system cannot stop you either, because there is no way, at runtime, to determine that this list was supposed to be a list of integers only. 如果Java编译器没有在编译时阻止您执行此操作,则运行时类型系统也无法阻止您执行此操作,因为在运行时无法确定此列表仅应为整数列表。 The Java runtime would let you put whatever you want into this list, when it should only contain integers, because when it was created, it was declared as a list of integers. Java运行时允许您将只包含整数的所有内容放入此列表中,因为在创建时将其声明为整数列表。

As such, the designers of Java made sure that you cannot fool the compiler. 因此,Java的设计人员确保您不能欺骗编译器。 If you cannot fool the compiler (as we can do with arrays) you cannot fool the runtime type system either. 如果您不能欺骗编译器(就像我们对数组所做的那样),那么您也不能欺骗运行时类型系统。

As such, we say that generic types are non-reifiable . 因此,我们说泛型是不可更改的

Evidently, this would hamper pollymorphism as well pointed out. 显然,这也会阻碍多形性。 The solution is to learn to use two powerful features of Java generics known as covariance and contravariance. 解决方案是学习使用Java泛型的两个强大功能,即协方差和逆方差。

Covariance 协方差

With covariance you can read items from a structure, but you cannot write anything into it. 使用协方差,您可以从结构中读取项目,但不能在其中写入任何内容。 All these are valid declarations. 所有这些都是有效的声明。

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

And you can read from myNums : 您可以从myNums读取:

Number n = myNums.get(0);

Because you can be sure that whatever the actual list contains, it can be upcasted to a Number (after all anything that extends Number is a Number, right?) 因为您可以确定实际列表中包含的内容,都可以将其向上转换为Number(所有扩展Number的东西都是Number,对吗?)

However, you are not allowed to put anything into a covariant structure. 但是,不允许您将任何内容放入协变结构中。

myNumst.add(45L);

This would not be allowed, because Java cannot guarantee what is the actual type of the real object. 这是不允许的,因为Java无法保证实际对象的实际类型是什么。 It can be anything that extends Number, but the compiler cannot be sure. 它可以是扩展Number的任何内容,但是编译器不能确定。 So you can read, but not write. 因此您可以阅读,但不能书写。

Contravariance 逆差

With contravariance you can do the opposite. 有了相反性,您可以做相反的事情。 You can put things into a generic structure, but you cannot read out from it. 您可以将事物放入通用结构中,但不能从中读出。

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

In this case, the actual nature of the object is a List of Objects, and through contravariance, you can put Numbers into it, basically because numbers have Object as the common ancestor. 在这种情况下,对象的实际性质是“对象列表”,通过逆变,您可以将数字放入其中,这主要是因为数字以对象为共同祖先。 As such, all Numbers are objects, and therefore this is valid. 因此,所有数字都是对象,因此这是有效的。

However, you cannot safely read anything from this contravariant structure assuming that you will get a number. 但是,假设您将得到一个数字,那么您将无法安全地从此反结构中读取任何内容。

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

As you can see, if the compiler allowed you to write this line, you would get a ClassCastException at runtime. 如您所见,如果编译器允许您编写此行,则在运行时将收到ClassCastException。

Get/Put Principle 获取/放置原理

As such, use covariance when you only intend to take generic values out of a structure, use contravariance when you only intend to put generic values into a structure and use the exact generic type when you intend to do both. 因此,在仅打算将通用值从结构中取出时,请使用协方差;在仅打算将通用值放入结构中时,请使用逆方差;当您打算同时使用两者时,请使用确切的通用类型。

The best example I have is the following that copies any kind of numbers from one list into another list. 我最好的例子是将以下任何一种数字从一个列表复制到另一个列表。

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

Thanks to the powers of covariance and contravariance this works for a case like this: 得益于协方差和逆方差的强大功能,它可以在以下情况下工作:

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

I agree it's confusing. 我同意这令人困惑。 Here's what could go wrong if that type of statement were allowed: 如果允许该类型的语句,这可能会出错:

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!

Oh. 哦。 Yes, you true. 是的,你是真的。 your code is right in Polymorphism thinking. 您的代码在多态思维中是正确的。 But, look at my code that I use for a long time when I just a novie. 但是,看看我只是个菜鸟时使用了很长时间的代码。 And you will see why you should thank to Collection : 您将了解为什么要感谢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];        
    }

}

As you see my above code, Collections is so "intelligent" when prevent you use List<Animal> tigerList = new ArrayList<Tiger>(); 如您在上面的代码中所见,当您阻止使用List<Animal> tigerList = new ArrayList<Tiger>();时, Collections是如此“智能” List<Animal> tigerList = new ArrayList<Tiger>();

You should imagine if someone use: tigerList.add(a Lion, a Cat,......); 您应该想象是否有人使用: tigerList.add(a Lion, a Cat,......); --->ERROR. --->错误。

So, to Sumarize, here is the different: 因此,总结一下,这是不同的:

ARRAY: check at RUN-TIME. 数组:在运行时检查。 You will feel more comfortable but DANGEROUS 您会感到更舒适,但危险

COLLECTIONS: check at COMPILE-TIME. 集合:在编译时检查。 You will feel angry because it notice error. 您会生气,因为它会注意到错误。 But, you will prevent errors when Running. 但是,您将在运行时防止错误。 !!!! !!!!

Maybe below post is over of your question. 也许下面的帖子已经解决了您的问题。 But I suggest you use WildCard like: 但我建议您使用WildCard,例如:

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

Yes. 是。 You might see the idea behind this line. 您可能会在此行后面看到这个想法。 But, the MOST INTERESTING THING is: it will prevent you change the List. 但是,最有趣的事情是:它将阻止您更改列表。 in this case, add method. 在这种情况下,请add方法。 For example: 例如:

tigerList.add(TIGER); ERROR

yes. 是。 It will prevent you add a tiger, too :) 这也将阻止您添加老虎:)

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

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