java如何往List<? extends number>中加入元素?体会范型集合父子关系以及范型通配符的使用

发布时间 2023-06-13 14:29:43作者: r1-12king

以下来自一个stackoverflow的一个问答,写的很清楚。

基本上就是子类集合的引用付给父类引用,如果父类的引用变量声明的是<? extends Parent>, 则父类引用变量只能对集合进行读操作,读出来的变量是Parent类型,这是因为不确定该父类引用变量指向的是什么类型的集合,可以是Child1,也可以Child2,如果生命一个元素Parent p1,然后要加入集合,就会出错。

而如果父类变量声明的形式是<? super Child>,则通过该引用变量只能近些写操作,不能进行读操作。原理也是类似的。如果生命一个变量, Child c1, 将c1加入该集合,是没有问题的,因为集合的元素类型是Child的父类型。而如果读取,则读出来的元素就不能按照Child类型来接,所以读不可以。

 


 

Sorry, but you can't.

The wildcard declaration of List<? extends Number> foo3 means that the variable foo3 can hold any value from a family of types (rather than any value of a specific type). It means that any of these are legal assignments:

List<? extends Number> foo3 = new ArrayList<Number>();  // Number "extends" Number
List<? extends Number> foo3 = new ArrayList<Integer>(); // Integer extends Number
List<? extends Number> foo3 = new ArrayList<Double>();  // Double extends Number

So, given this, what type of object could you add to List foo3 that would be legal after any of the above possible ArrayList assignments:

  • You can't add an Integer because foo3 could be pointing at a List<Double>.
  • You can't add a Double because foo3 could be pointing at a List<Integer>.
  • You can't add a Number because foo3 could be pointing at a List<Integer>.

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.

The reverse logic applies to super, e.g. List<? super T>. These are legal:

List<? super Number> foo3 = new ArrayList<Number>(); // Number is a "super" of Number
List<? super Number> foo3 = new ArrayList<Object>(); // Object is a "super" of Number

You can't read the specific type T (e.g. Number) from List<? super T> because you can't guarantee what kind of List it is really pointing to. The only "guarantee" you have is you are able to add a value of type T (or any subclass of T) without violating the integrity of the list being pointed to.

 


 

 

The perfect example of this is the signature for Collections.copy():

public static <T> void copy(List<? super T> dest, List<? extends T> src)

Notice how the src list declaration uses extends to allow me to pass any List from a family of related List types and still guarantee it will produce values of type T or subclasses of T. But you cannot add to the src list.

The dest list declaration uses super to allow me to pass any List from a family of related List types and still guarantee I can write a value of a specific type T to that list. But it cannot be guaranteed to read the values of specific type T if I read from the list.

So now, thanks to generics wildcards, I can do any of these calls with that single method:

// copy(dest, src)
Collections.copy(new ArrayList<Number>(), new ArrayList<Number>());
Collections.copy(new ArrayList<Number>(), new ArrayList<Integer>());
Collections.copy(new ArrayList<Object>(), new ArrayList<Number>());
Collections.copy(new ArrayList<Object>(), new ArrayList<Double>());

 


 

 

Consider this confusing and very wide code to exercise your brain. The commented out lines are illegal and the reason why is stated to the extreme right of the line (need to scroll to see some of them):

 1   List<Number> listNumber_ListNumber  = new ArrayList<Number>();
 2 //List<Number> listNumber_ListInteger = new ArrayList<Integer>();                    // error - can assign only exactly <Number>
 3 //List<Number> listNumber_ListDouble  = new ArrayList<Double>();                     // error - can assign only exactly <Number>
 4   
 5   List<? extends Number> listExtendsNumber_ListNumber  = new ArrayList<Number>();
 6   List<? extends Number> listExtendsNumber_ListInteger = new ArrayList<Integer>();
 7   List<? extends Number> listExtendsNumber_ListDouble  = new ArrayList<Double>();
 8   
 9   List<? super Number> listSuperNumber_ListNumber  = new ArrayList<Number>();
10 //List<? super Number> listSuperNumber_ListInteger = new ArrayList<Integer>();      // error - Integer is not superclass of Number
11 //List<? super Number> listSuperNumber_ListDouble  = new ArrayList<Double>();       // error - Double is not superclass of Number
12   
13  
14 //List<Integer> listInteger_ListNumber  = new ArrayList<Number>();                  // error - can assign only exactly <Integer>
15   List<Integer> listInteger_ListInteger = new ArrayList<Integer>();
16 //List<Integer> listInteger_ListDouble  = new ArrayList<Double>();                  // error - can assign only exactly <Integer>
17   
18 //List<? extends Integer> listExtendsInteger_ListNumber  = new ArrayList<Number>(); // error - Number is not a subclass of Integer
19   List<? extends Integer> listExtendsInteger_ListInteger = new ArrayList<Integer>();
20 //List<? extends Integer> listExtendsInteger_ListDouble  = new ArrayList<Double>(); // error - Double is not a subclass of Integer
21   
22   List<? super Integer> listSuperInteger_ListNumber  = new ArrayList<Number>();
23   List<? super Integer> listSuperInteger_ListInteger = new ArrayList<Integer>();
24 //List<? super Integer> listSuperInteger_ListDouble  = new ArrayList<Double>();     // error - Double is not a superclass of Integer
25  
26  
27   listNumber_ListNumber.add(3);             // ok - allowed to add Integer to exactly List<Number>
28   
29   // These next 3 are compile errors for the same reason:
30   // You don't know what kind of List<T> is really
31   // being referenced - it may not be able to hold an Integer.
32   // You can't add anything (not Object, Number, Integer,
33   // nor Double) to List<? extends Number>      
34 //listExtendsNumber_ListNumber.add(3);     // error - can't add Integer to *possible* List<Double>, even though it is really List<Number>
35 //listExtendsNumber_ListInteger.add(3);    // error - can't add Integer to *possible* List<Double>, even though it is really List<Integer>
36 //listExtendsNumber_ListDouble.add(3);     // error - can't add Integer to *possible* List<Double>, especially since it is really List<Double>
37   listSuperNumber_ListNumber.add(3);       // ok - allowed to add Integer to List<Number> or List<Object>
38   
39   listInteger_ListInteger.add(3);          // ok - allowed to add Integer to exactly List<Integer> (duh)
40   // This fails for same reason above - you can't
41   // guarantee what kind of List the var is really
42   // pointing to
43 //listExtendsInteger_ListInteger.add(3);   // error - can't add Integer to *possible* List<X> that is only allowed to hold X's
44   
45   listSuperInteger_ListNumber.add(3);      // ok - allowed to add Integer to List<Integer>, List<Number>, or List<Object>
46   listSuperInteger_ListInteger.add(3);     // ok - allowed to add Integer to List<Integer>, List<Number>, or List<Object>

 



问题一:

List<? extends Number>  list = new ArrayList<>();
list .add(100);  //这样的代码会报错的

 

为啥这样的List就不能调用add函数呢?    

奇怪,明明100是Integer对象类型,然后Integer是Number的子类,符合<? extends Number>上线限制的原则啊,,,为啥就报错了呢?

这样的问题,网上很多回答的,但总是说类型不安全,导致不让add。。。。说的一塌糊涂,,,让我想了好久,最后我根据我的理解,整理出来,方便大家理解,有哪里说的不对的,欢迎指正。

 

答:1 . List<? extends Number> list 就是对加入的元素进行上限限制,,,只要是Number或者Number的子类都事可以的,,,但具体是哪一个? 大哥, 这谁知道啊,,,当然编译器也是不知道的,。。。。那编译器都不知道是什么类型,你就直接add一个Integer类型的,,,要是人家后面确定是Float类型的,那不就是搞错了吗? 这样的原因,导致java编译器直接就不让add了,从而避免了该问题的发生。

2. 还有种解释是,,,现在JAVA的强类型检测,当编辑器遇到<? extends Number>时候,他只知道有东西要加入了,至于具体是什么类型,就不得而知了,所以在声明该变量的时候,只是存了一个变量加入类型的标记符,也就是占位符,具体是什么类型,还不能确定。。。当list调用add函数加入的时候, 会进行类型判断的,这个时候,因为上面保存的只是占位符,却没有真正的标记变量的类型。。。导致没法判断,就这样JAVAl数据类型的强类型问题问题,就会导致报错和各种问题,所以JAVA就直接不让add了,从而避免了该问题的发生。

 

问题二:

List<? super Number> listb = new ArrayList<>();
Integer a = 10;
listb.add(a);

 

listb.add((Object)a)    //这个的add会报错的

为啥这里的listb.add(a)不会报错,而listb.add(Object)对象会报错呢?

 

答:先说明下这里色List<? super Number> list,,,这个就是泛型的下线限制符。。。里面可以存储的对象是Number类以及Number类的父类。。。至于具体是哪个类型,就不得而知了。这就更奇怪了,为啥Integer类存储成功了,而Object类存储报错了。。。这是为啥呢?

首先先说,为啥add Integer对象成功了。。。其实是这样的,因为JAVA的继承原理,对应的属性图都是从上到下的方式,Integer对象肯定是Number对象。。。而List<? super Number> list可以存储的就是Number类或者Number类的父类,但具体是哪个类型,就不得而知了。。。当然编译器也是不知道的。。。但就是因为JAVA的继承原理,让每一个Number子类都同时是Number类,,,同时也是所以Number父类的子类。。。这不就是完全符合了List<? super Number>list 可以存储的要求了吗? 因为Number子类,全部可以看成是Number类,然后Number类都是可以符合该List要求的。。。。那这样明显就可以存储成功了啊。。。

然后在说add Object失败的问题,很明显,在申明一次,List<? super Number> list存储的只能是Number类或者Number类的父类,但具体是哪个类??? 谁知道啊,,,有谁知道吗??? 鬼都不知道,编辑器当然也是不知道的

。。。这个时候,你add object对象,编辑器都不知道里面存储的数据类型,因为JAVA的强类型和Java类型检测机制,,,这里就会报错啦。。。。

问题三:

基于上面的考虑,List<? extends Number>list 都不能add,,,那有这个东西有什么用呢?   这个自己体会吧,,,简单说明一下,抽象类和接口都不能new对象,怎么就那么大作用呢? 道理是一样的,,,比如如下:

List<? super Number>  list = new Arrary<>();
List<Integer>  listb  = new Arrary<>();
list  = listb;

这就是用到的地方,对类型进行限制。。。其他的自己体会下吧,,,

    

总结:

当考虑到要add时候,对于安全性的考虑,应该使用泛型的<? super XXX>模式,当考虑到要对list进行get类型显示的时候,就要考虑用到<? extends XXX>,这样的泛型才能起到,类型安全,并且简洁,程序员开发直观的效果!!!!!!