简体   繁体   English

为什么和多态泛型类型

[英]why and what of polymorphic generic types

When should one use generic polymorphic types like these and what are its implications? 什么时候应该使用像这样的通用多态类型,它的含义是什么?

 1. List<? super Dog> list = new ArrayList<Animal>();
 2. List<? extends Animal> list = new ArrayList<Dog>();
 3. List<?> list = new ArrayList<Dog>();

Would anyone use something like 有人会用这样的东西

List<? super Dog> list = new ArrayList<Dog>();
List<? extends Animal> list = new ArrayList<Animal>();

Note: I understand when people use List<? super Dog> 注意:我理解人们何时使用List<? super Dog> List<? super Dog> or List<? extends Animal> List<? super Dog>List<? extends Animal> List<? extends Animal> in method definitions. 在方法定义中List<? extends Animal> But what I don't understand is polymorphic generic typed object creation. 但我不明白的是多态泛型类型对象创建。

The reasons for this are based on how Java implements generics. 其原因是基于Java如何实现泛型。

An Arrays Example 一个数组示例

With arrays you can do this (arrays are covariant) 使用数组,您可以执行此操作(数组是协变的)

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 Because you're trying to put a double into an integer array (regardless of being accessed through a number reference). 因为您试图将double放入整数数组(无论是通过数字引用访问)。

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 actual 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 not committing heap pollution. 但重要的是,由于在运行时没有类型信息,因此无法确保我们不会造成堆污染。

For instance, 例如,

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

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

If the Java compiler does not stop you from doing this, 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 polymorphism. 显然,这会妨碍多态性。 Consider the following example: 请考虑以下示例:

static long sum(Number[] numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}

Now you could use it like this: 现在你可以像这样使用它:

Integer[] myInts = {1,2,3,4,5};
Long[] myLongs = {1L, 2L, 3L, 4L, 5L};
Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0};

System.out.println(sum(myInts));
System.out.println(sum(myLongs));
System.out.println(sum(myDoubles));

But if you attempt to implement the same code with generic collections, you will not succeed: 但是,如果您尝试使用泛型集合实现相同的代码,则不会成功:

static long sum(List<Number> numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}

You would get compiler erros if you try to... 如果你试图...你会得到编译器错误

List<Integer> myInts = asList(1,2,3,4,5);
List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L);
List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0);

System.out.println(sum(myInts)); //compiler error
System.out.println(sum(myLongs)); //compiler error
System.out.println(sum(myDoubles)); //compiler error

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的数字都是数字,对吧?)

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

myNumst.add(45L); //compiler error

This would not be allowed, because Java cannot guarantee what is the actual type of the object in the generic structure. 这是不允许的,因为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 all numbers have Object as their common ancestor. 在这种情况下,对象的实际性质是对象列表,并且通过逆变,您可以将Numbers放入其中,主要是因为所有数字都将Object作为它们的共同祖先。 As such, all Numbers are objects, and therefore this is valid. 因此,所有Numbers都是对象,因此这是有效的。

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. 我所拥有的最好的例子是,将任何类型的数字从一个列表复制到另一个列表中。 It only gets items from the source, and it only puts items in the destiny. 它只从源头获取物品,它只物品放入命运。

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<Double> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();

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

They would use: 他们会使用:

  1. If they need a list of objects that are of a superclass of Dog . 如果他们需要一个属于Dog超类的对象列表。 Like Animal , for instance. 比如Animal

  2. If they need a list of objects that are animals (subclass of Animal ). 如果他们需要那些动物(子类对象的列表Animal )。 Like Dog , for instance. 比如Dog

  3. If they need a list of objects, period. 如果他们需要一个对象列表,期限。 Which can incidentally be a list of dogs, for instance. 例如,这可能是一个狗列表。

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

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