泛型程序设计
List<?>与List<T>、List<Object>、List
能说说他们之间的区别吗?!我在第一次看到这几个东西的时候也会摸不着头脑。。。甚至会将他们弄混!
-
首先
?
是一个类型通配符,T
可以看做是一个类型指代,Object
是具体的泛型类型- List<?>表示有一个List,但是我不清楚里面元素是什么类型!
- List则表示一个List,而其中的元素类型都是T类型(或者其子类型),并且这个T是可以唯一确定的!
- List表示可以存放任何数据的List,因为Object是任何类型的父类!
-
**List<?>在经过赋值前,可以接收任何类型的集合赋值,但是一旦赋值后,就不能往集合中add值了!**但是允许清除和删除!
-
再来看看List和List<Object>,很多人将它们俩混为一谈
public class GenericTest01 { public static void main(String[] args) { List list = new ArrayList(); list.add(Integer.valueOf(22)); list.add("Hello"); list.add(3.56); List<Object> objectList = list; objectList.add(Integer.valueOf(44)); objectList.add("World"); objectList.add(4.77); objectList.forEach(System.out::println); } }
从这段代码运行结果来看确实两者好像没什么区别!但是遇到下面这种情况,就会出现问题
List<Integer> integerList = list;
这样写编译只是报警但是运行就会出错!
因为List在没有写明泛型类型时候,是可以往里面任意存值的,但是无论怎么存都逃不出Object的手掌心!所以将他赋值给一个List声明不会出错,但是赋值给一个List,就相当于贸然认为List中都是Integer对象,当然如果确实都是Integer(或其子类)倒还好,如果不是,则会在类型转换的时候抛出
ClassCastException
异常
关于泛型约束<? extends T>与<? super T>
List最大的问题就是只能放置一种类型。如果需要放置多种受泛型约束的类型时就要用到<? extends T>
、<? super T>
。但是在使用时我们会误解其所代表的意思!并且两者有明确的适用场景。
我们现在定义三个类,根据继承关系依次为:Animal
、Cat
、Garfield
<? extends T>
**可以接收任何泛型为T或其子类的集合,即泛型上界为T。**我们很容易误解为它是一个存放T及其子类的集合。
但是你在试图运行以下代码的时候,却会发现add操作是不允许的!
List<? extends Cat> extendsCat = new ArrayList<>();
extendsCat.add(new Garfield());
extendsCat.add(new Cat());
因为<? extends T>指的是一个具有继承自T的类型的泛型列表。**即<? extends T>只是对List的泛型做一个限制和检查!要求List的泛型类型必须是T或者其子类!**所以你会看到这样的代码:
extendsCat = new ArrayList<Cat>();
extendsCat = new ArrayList<Garfield>();
// 不允许
extendsCat = new ArrayList<Animal>();
extendsCat = new ArrayList<Object>();
前面两行的赋值是没有问题的,但是后面两行则是不符合规范的!因为Animal、Object均不是继承自Cat的!
明白这个道理以后,再说说为什么一旦赋值后,集合就不能向泛型声明为<? extends T>的列表中添加元素了:
因为<? extends T>可以接受集合的泛型类型是不稳定的,但是都有统一的上界。但即便是上界统一,程序也不敢让你贸然向一个未知的泛型集合添加数据,一旦你添加的数据类型“高于”真实的泛型类型,那么类型转换就会抛出异常!
例如:
List<? extends Cat> extendsCat = new ArrayList<>();
extendsCat = new ArrayList<Cat>();
extendsCat = new ArrayList<Garfield>();
以上三种赋值都是可以的,但是如果真实代码中赋值语句是最后一条,那么真实泛型就是Garfield。
如果此时允许你向集合中添加数据,你能得知的就只是这个集合的泛型类型是继承自Cat的,如果你贸然add一个Cat对象,程序就会出现异常!!因为你无法确实你所添加的数据就一定是集合的真实类型或其子类!
但是null
是可以添加的,因它可以表示任何对象。
然后我们再来看看取数据,既然我们清楚集合的泛型类型上限是T,那么无论集合的真实类型是什么,可以确保我们取出的数据必定是T或其子类,所以取出的数据的类型默认是T!
Cat obj = extendsCat.get(0);
所以说**<? extends T>的场景是put功能受限,是Get First,适用于消费集合元素为主的场景!**
<? super T>
与<? extends T>,刚好相反,它要求赋值的集合的泛型类型必须是T或其父类!即泛型的下界是T。
那么这些赋值语句的结果就会与刚才大不相同:
List<? super Cat> superCat = new ArrayList<>();
superCat = new ArrayList<Animal>();
superCat = new ArrayList<Object>();
superCat = new ArrayList<Cat>();
// 不允许
superCat = new ArrayList<Garfield>();
很明显最后一个赋值语句是不能通过的!因为Garfield并不是Cat的父类!
并且现在你会发现add可以使用了,但是只允许添加Cat或其子类对象!这是为什么?!(明明都说了泛型是T的父类,为什么反而只能添加T及其子类呢?!)
这就是我经常陷入的误解区域,<? super T>只能说明它所指向的集合的泛型类型是T的父类,但是不一定说是集合内的元素就都是Cat的父类!!
想要走出这个误区,我们得站在设计者的角度看:
<? super T>声明能够表示这个集合的泛型下界是Cat,但是无法确保它的上界。既然允许用户往里面添加数据,那么 在无法得知集合真实泛型的情况下,最保险的数据就是这个下界及其子类对象。这样就不会导致类型转换抛出异常!
例如:
List<? super Cat> superCat = new ArrayList<Cat>();
superCat.add(new Animal());
我们在添加数据的时候,如果添加了这个下界的父类型对象,就可能出现数据类型“高于”真实集合泛型的情况,进而导致类型转换失败抛出异常!
但是我们在取数据时,会发现一个有意思的现象。无论集合的真实泛型,我们通过<? super T>声明的集合取出的数据都是Object
!如果你能懂前面“extends T取出的对象都是T”是怎么回事,理解这个也就是分分钟的事情。
因为通过<? super T>的声明我们是无法确定集合的真实泛型的,我们只清楚集合泛型的下限是T,但是对我们取数据没有任何价值,因为集合里面的数据可能是T的父类,也可能是T的子类!所以保险起见,因为所有的对象的共同祖先是Object,那么索性将取出的数据都视为Object,准不会出错!
解这个也就是分分钟的事情。
因为通过<? super T>的声明我们是无法确定集合的真实泛型的,我们只清楚集合泛型的下限是T,但是对我们取数据没有任何价值,因为集合里面的数据可能是T的父类,也可能是T的子类!所以保险起见,因为所有的对象的共同祖先是Object,那么索性将取出的数据都视为Object,准不会出错!
所以说**<? super T>的场景是get功能受限,是Put First,适用于以生产集合元素为主的场景!**