Java中 ? extends T 和 ? super T 的区别

前言:向上转型是安全的,向下转型是不安全的,除非你知道List中的真实类型,否则向下转型就会报错。

extends

List<? extends Number> foo3意味着下面的赋值语句都是合法的:

List<? extends Number> foo3 = new ArrayList<Number>();  // Number "extends" Number (in this context)
List<? extends Number> foo3 = new ArrayList<Integer>(); // Integer extends Number
List<? extends Number> foo3 = new ArrayList<Double>();  // Double extends Number
1.读取

给定上述可能的赋值语句,能保证你从List foo3中取出什么样类型的对象?

  • 你可以读取一个Number对象,因为上面任意一个list都包含Number对象或者Number子类的对象(上面的Number、Integer、Double都可以转型成Number,并且是安全的,所以读取总是可以的)。如下代码就不会报错:
        List<? extends Number> foo4 = new ArrayList<Integer>();
        Number number = foo4.get(0);
  • 你不能读取一个Integer对象,因为foo3可能指向的是List<Double>(与其运行时发现Double转成Integer报错,不如编译时就不让从foo3中取Integer对象)。如下代码编译时会报Incompatible types错的:
        List<? extends Number> foo4 = new ArrayList<Integer>();
        Integer number = foo4.get(0);

因为编译的时候编译器只知道foo4引用是一个List<? extends Number>,要到运行时才会绑定到new ArrayList<Integer>(),所以编译的时候是无法判断foo4指向的List中到底是什么类型,唯一能确定的就是这个类型是Number的子类(或者就是Number类)。

  • 你也不能读取一个Double对象,因为foo3可能指向的是List<Integer>
2.写入

给定上述可能的赋值语句,你能往List foo3中添加什么类型的对象从而保证它对于所有可能的ArrayList都是合法的呢?

  • 你不能添加一个Integer对象,因为foo3可能指向的是List<Double>。如下代码是会编译报错的:
        List<? extends Number> foo4 = new ArrayList<Integer>();
        foo4.add(new Integer(1));

因为编译期间是无法知道foo4指向的ArrayList中到底放的是什么类型,只有到运行时才知道(就是Java所谓的晚绑定或运行时绑定)。与其到运行时发现往一个ArrayList<Double>中add一个Integer导致抛出类型转换异常,倒不如编译时就报错,即使ArrayList中放的就是Integer类型。

  • 你不能添加一个Double对象,因为foo3可能指向的是List<Integer>
  • 你不能添加一个Number对象,因为foo3可能指向的是List<Integer>

总结一下:你不能往List<? extends T>中添加任何对象,因为你不能保证List真正指向哪个类型,所以不能确定添加的对象就是List所能接受的类型。能保证的,仅仅是你可以从List中读取的时候,你获得的肯定是一个T类型的对象(即使是T类型的子类对象也是T类型的)。

Super

现在考虑List<? super T>
包含通配符的声明List<? super Integer> foo3意味着下面任何一个赋值语句都是合法的:

List<? super Integer> foo3 = new ArrayList<Integer>();  // Integer is a "superclass" of Integer (in this context)
List<? super Integer> foo3 = new ArrayList<Number>();   // Number is a superclass of Integer
List<? super Integer> foo3 = new ArrayList<Object>();   // Object is a superclass of Integer
1.读取

给定上述可能的赋值语句,当读取List foo3中的元素的时候,你能保证接收到什么类型的对象呢?

  • 你不能保证是一个Integer对象,因为foo3可能指向一个List<Number>或者List<Object>
  • 你不能保证是一个Number对象,因为foo3可能指向一个List<Object>
  • 你能保证的仅仅是它一定是一个Object类的实例或者Object子类的实例(但是你不知道到底是哪个子类)。
2.写入

给定上述可能的赋值语句,你能往List foo3中添加什么类型的对象从而保证它对于所有可能的ArrayList都是合法的呢?

  • 你可以添加一个Integer实例,因为Integer类型对于上述所有的list都是合法的。
  • 你可以添加任何Integer子类的实例,因为一个Integer子类的实例都可以向上转型成上面列表中的元素类型。
  • 你不可以添加Double类型,因为foo3可能指向的是ArrayList<Integer>
  • 你不可以添加Number类型,因为foo3可能指向的是ArrayList<Integer>
  • 你不可以添加Object类型,因为foo3可能指向的是ArrayList<Integer>

PECS

PECS是"Producer Extends,Consumer Super"的缩写。

  • "Producer Extends"的意思是,如果你需要一个List去生产T类型values(也就是说你需要去list中读取T类型实例),你需要声明这个List中的元素为? extends T,例如List<? extends Integer>,但是你不能往里面添加元素。
  • "Consumer Super"的意思是,如果你需要一个List去消费T类型values(也就是说你需要往list中添加T类型实例),你需要声明这个List中的元素为? super T,例如List<? super Integer>。但是不能保证你从这个list中读取出来对象类型。
  • 如果你既需要往list中写,也需要从list中读,那么你就不能用通配符?,必须用精确的类型,比如List<Integer>

原文链接:
https://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends-t-in-java


原文内容如下:

extends

The wildcard declaration of List<? extends Number> foo3 means that any of these are legal assignments:

List<? extends Number> foo3 = new ArrayList<Number>();  // Number "extends" Number (in this context)
List<? extends Number> foo3 = new ArrayList<Integer>(); // Integer extends Number
List<? extends Number> foo3 = new ArrayList<Double>();  // Double extends Number
  1. Reading - Given the above possible assignments, what type of object are you guaranteed to read from List foo3:
  • You can read a Number because any of the lists that could be assigned to foo3 contain a Number or a subclass of Number.
  • You can’t read an Integer because foo3 could be pointing at a List.
  • You can’t read a Double because foo3 could be pointing at a List.
  1. Writing - Given the above possible assignments, what type of object could you add to List foo3 that would be legal for all the above possible ArrayList assignments:
  • You can’t add an Integer because foo3 could be pointing at a List.
  • You can’t add a Double because foo3 could be pointing at a List.
  • You can’t add a Number because foo3 could be pointing at a List.
  • You can’t add any object to List<? extends T> because you can’t guarantee what kind of List it is really pointing to, so you can’t guarantee that the object is allowed in that List. The only “guarantee” is that you can only read from it and you’ll get a T or subclass of T.

super

Now consider List <? super T>.

The wildcard declaration of List<? super Integer> foo3 means that any of these are legal assignments:

List<? super Integer> foo3 = new ArrayList<Integer>();  // Integer is a "superclass" of Integer (in this context)
List<? super Integer> foo3 = new ArrayList<Number>();   // Number is a superclass of Integer
List<? super Integer> foo3 = new ArrayList<Object>();   // Object is a superclass of Integer
  1. Reading - Given the above possible assignments, what type of object are you guaranteed to receive when you read from List foo3:
  • You aren’t guaranteed an Integer because foo3 could be pointing at a List or List.
  • You aren’t guaranteed a Number because foo3 could be pointing at a List.
  • The only guarantee is that you will get an instance of an Object or subclass of Object (but you don’t know what subclass).
  1. Writing - Given the above possible assignments, what type of object could you add to List foo3 that would be legal for all the above possible ArrayList assignments:
  • You can add an Integer because an Integer is allowed in any of above lists.
  • You can add an instance of a subclass of Integer because an instance of a subclass of Integer is allowed in any of the above lists.
  • You can’t add a Double because foo3 could be pointing at an ArrayList.
  • You can’t add a Number because foo3 could be pointing at an ArrayList.
  • You can’t add an Object because foo3 could be pointing at an ArrayList.

PECS
Remember PECS: “Producer Extends, Consumer Super”.

  • “Producer Extends” - If you need a List to produce T values (you want to read Ts from the list), you need to declare it with ? extends T, e.g. List<? extends Integer>. But you cannot add to this list.

  • “Consumer Super” - If you need a List to consume T values (you want to write Ts into the list), you need to declare it with ? super T, e.g. List<? super Integer>. But there are no guarantees what type of object you may read from this list.

  • If you need to both read from and write to a list, you need to declare it exactly with no wildcards, e.g. List.

Example
Note this example from the Java Generics FAQ. Note how the source list src (the producing list) uses extends, and the destination list dest (the consuming list) uses super:

public class Collections { 
  public static <T> void copy(List<? super T> dest, List<? extends T> src) {
      for (int i = 0; i < src.size(); i++) 
        dest.set(i, src.get(i)); 
  } 
}

猜你喜欢

转载自blog.csdn.net/lzufeng/article/details/83589665