为什么以及多态的泛型类型 [英] why and what of polymorphic generic types

查看:136
本文介绍了为什么以及多态的泛型类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

什么时候应该使用这样的泛型多态类型?它的含义是什么?

  1.列表<?超级狗> list = new ArrayList< Animal>(); 
2.列表< ;?延伸动物> list = new ArrayList< Dog>();
3.列表<?> list = new ArrayList< Dog>();

是否有人会使用类似于

 列表与LT ;?超级狗> list = new ArrayList< Dog>(); 
列表< ;?延伸动物> list = new ArrayList< Animal>();

注意:
当人们使用 List<超级狗> 列表< ;?在方法定义中扩展Animal> 。但我不明白的是多态泛型类型对象的创建。

解决方案

原因在于Java如何实现泛型。
$ b

数组示例



协变)

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

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

 数字[0] = 3.14; //堆污染的尝试

最后一行编译得很好,但是如果你运行这段代码,你可以得到 ArrayStoreException 。因为你试图把double放到一个整数数组中(不管是通过数字引用来访问)。

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



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



Java泛型的问题



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



但重要的这里指的是,因为在运行时没有类型信息,所以没有办法确保我们不会造成堆污染。



例如,

 列表<整数> myInts = new ArrayList< Integer>(); 
myInts.add(1);
myInts.add(2);

列表<号码> myNums = myInts; //编译器错误
myNums.add(3.14); // heap polution

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



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



因此,我们说泛型是不可否认

显然,这会妨碍多态性。考虑下面的例子:

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

现在你可以像这样使用它:

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

但是,如果您尝试使用泛型集合实现相同的代码,您将不会成功:

  static long sum(List< Number>数字){
long summation = 0;
for(Number number:numbers){
summation + = number.longValue();
}
返回总和;

$ / code>

如果你试图......你会得到编译器错误...

 列表<整数> myInts = asList(1,2,3,4,5); 
列表< Long> myLongs = asList(1L,2L,3L,4L,5L);
列表< Double> myDoubles = asList(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)); //编译器错误

解决方案是学习使用称为协方差的Java泛型的两个强大功能,

协方差

使用协方差可以读取结构中的项目,但你不能写任何东西。所有这些都是有效的声明。

 列表< ;?扩展Number> myNums = new ArrayList< Integer>(); 
列表< ;?扩展Number> myNums = new ArrayList< Float>()
List< ;?扩展Number> myNums = new ArrayList< Double>()

你可以从 myNums

  Number n = myNums.get(0); 

因为您可以确定无论实际列表中包含什么,它都可以上传到一个Number(after所有延伸的数字都是一个数字,对吧?)

然而,你不允许将任何东西放入协变结构中。

  myNumst.add(45L); //编译错误

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



逆变函数

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

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

列表< ;?超级号码> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);

在这种情况下,对象的实际性质是一个对象列表,通过逆变,你可以把数字放进去,主要是因为所有数字都有Object作为它们的共同祖先。因此,所有数字都是对象,因此这是有效的。



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

  Number myNum = myNums.get(0); //编译器错误

正如你所看到的,如果编译器允许你写这行,在运行时会得到一个ClassCastException。



获取/放置原则

,当你只打算从结构中取出泛型值时使用协变性,当你只打算将泛型值放入结构中时使用变换性,并且当你打算同时使用确切的泛型类型时使用协变性。



我最好的例子是将任何一种数字从一个列表复制到另一个列表中。它只从源获取项,并且只将项放入命运中。

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




$ b $ p
$ b

由于协变和反变换的作用,像这样的情况:

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

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


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> or List<? extends Animal> in method definitions. But what I don't understand is polymorphic generic typed object creation.

解决方案

The reasons for this are based on how Java implements generics.

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. Because you’re trying to put a double into an integer array (regardless of being accessed through a number reference).

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

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

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. 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.

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. 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.

As such, the designers of Java made sure that you cannot fool the compiler. 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.

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:

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

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. It can be anything that extends Number, but the compiler cannot be sure. 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. 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.

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

这篇关于为什么以及多态的泛型类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆