泛型总结
泛型是什么?
一句话说就是类型参数化。什么意思呢?参数化的意思就是咱们在定义的时候不知道具体的值,咱们在到咱们实际运行的时候才知道具体的值。类型参数化就是具体类型在定义的时候不知道,在实际运行的时候是肯定的某一个类型。url
Java 是如何实现泛型的?
泛型是不少高级语言都有的特性。根据定义,泛型在运行时表示同一个类型,咱们比较容易想到 List<A>
和 List<B>
用 2 个不一样的 Class 表示,这个是可行的,可是 Java 因为须要兼容支持旧的代码,并且在推出泛型前就提供了容器类,这种方式(List<A>
和 List<B>
用 2 个不一样的 Class)没法兼容之前的老代码,因此这个实现方法不适用。因此 Java 大佬们想了另一种方式来实现泛型,这种方式就是类型擦除
。.net
什么是泛型的类型擦除呢?
类型擦除就是在实际生成字节码的时候,编译器源码里面定义的 List<A>
变成了 List<Object>
,源码里面定义的 A Class
被擦除
了,变成了 Object
,同时在使用的时候,会强制类型转换,把取出来的 object
转成 A
的实例去使用。这就是类型擦除。code
初步看,泛型擦除好像是没什么大的问题,可是仔细想一想,在强制类型转换的时候,因为会丢掉类型的一些信息,会致使一些不符合预期的事情。好比有个基类 A,和它的两个子类 B 和 C ,而后咱们有下面的一段代码。get
List<A> listA = new ArrayList<A>(); listA.add(new B()); // 错误的,
第二行代码是不符合预期的,由于 listA
里面指望放的是 A
而不是 B
。 可是这个好像不太符合预期,咱们有时候但愿子类是能够放进容器里面的。可是若是支持这个操做的话,会发生什么呢?取出来来的是 B
仍是 C
?若是不能明确,那么就没有实现“泛型”。编译器
为了解决这个问题, Java 大佬们想了个方法,提出了一些通配符来解决这些问题。源码
泛型的通配符 ?
、extends
和 super
在理解通配符以前,咱们须要知道的是,通配符的发明是为了解决什么问题?至少要解决的一个问题是:容器里面放进去的是什么,取出来的就是什么。io
这个问题,其实分两步,放进去,是说放进去同一种类型的东西。取出来,是说取出同一种类型的东西。或者说,用到通配符的地方应该是在不一样的地方,一个地方把数据写到容器,另一个地方把数据从容器拿出来,若是实在同一个代码块里写入和读取数据到同一个容器,应该是知道具体类型的,是不须要用到通配符的。编译
?
通配符
?
通配符称为无限通配符,表示不肯定或者不关心类型。class
extends 通配符
通常称为上界通配符,表示的意思是:取值范围为 (某个类的子类, 某个类]。再想一想咱们以前说的,通配符要解决的问题?放进去的是什么,取出来的就应该是什么。放数据和取数据应用在不一样的场景。若是咱们在同一个场景,就不须要用到通配符了,由于类型是已知的。容器
经过上面的表述,容易推断出来 <? extends E>
的集合只能往外拿数据,由于取出来的必定是 E
,可是放进去的不知道是什么,多是 E
,也多是 E
的子类,若是容许往集合里面放东西,就不能保证放进去的是什么,拿出来的就是什么了。由于只能保证拿出来的是 E
。
这个特性也叫作协变。
super 通配符
通常称为下界通配符,表示的意思是:取值范围为 [某个类,这个类的父类)。结合上面小节的解释,能够推断出 <? super S>
的集合只能往里面放数据,而不能从里面拿东西,为何呢?由于 <? extends E>
解决的就是拿出来的问题啊,因此这个解决的就是放进去的问题啊,囧。里面放的是下限或者下限的子类。
这个特性也叫作逆变。
小结
通配符与一个规则, PE-CS
。
-
PE
简单的说,当只想从集合中获取元素,请把这个集合当作生产者,请使用<? extends T>,这就是 Producer extends 原则,PECS原则中的PE部分。集合生产元素后,就能够拿过来用了。 -
CS
简单的说,当你仅仅想增长元素到集合,把这个集合当作消费者,请使用<? super T>。这就是 Consumer super 原则,PECS原则中的CS部分。集合消费元素,这样就能够往里面放了 -
同时做为生产者和消费者的状况不存在,由于你能够指定具体的泛型。
参考资料: