大佬勇是我这几年的技术导师,不定期向我们传授"绝世武功",当然他的格局不像我那样只在Java那么小。但最近,他却被Java泛型的通配符
搞得一头雾水。事缘他最近在研究Java的java.util.Optional
类,而该类有很多泛型方法,例如:
public Optional map(Function super T, ? extends U> mapper)
这个方法的参数是Function
实现类的对象,Function
是一个函数式接口,它的唯一方法接受一个T类型
或T类型父类
的对象,T为Optional
的类型参数,返回值为U
或U的子类
。
他想彻底搞明白这个super
(下界限定)和extend
(上界限定)究竟是怎么回事,于是百度,结果,搜索出来的PECS原则
让他更加疑惑,于是强烈要求我向他解释清楚这究竟是怎么一回事,但要说服他又谈何容易。
PECS
是Producer Extends Consumer Super
的缩写,直译就是生产者
使用extends
,消费者
使用super
。
假设定义了如下方法:
public void initData(java.util.List super Number> list) {
list.add(1);
list.add(2L);
list.add(3.0F);
list.add(4.0D);
}
该方法接受一个java.util.List super Number> list
参数,即list参数的类型参数
最少也必须是Number
类型,以该参数作为第一人称
,它是一个消费者
,因为,程序往它里面添加元素。由于它的类型下限
是Number
,因此,Integer
、Long
、Float
及Double
必定可以被添加到list中。
在调用该方法时,类型参数可以是Number,也可以是Object,但绝不能是Number的子类,如下图:
如上图,list
是类型参数是Object
,符合 super Number>
的要求,因此可以作为initData
方法的参数;list2
类型参数是Number
,也符合 super Number>
的要求。Integer
,Long
,Float
和Double
都是Number
的子类
,而Number
是Object
的子类,因此,它们既能存到list中也能存到list2中了。
而list3
类型参数是Integer
,明显不符合 super Number>
的要求,因此编译器
会报错。
假设定义了如下方法:
public void readData(java.util.List extends Number> list) {
list.forEach(num -> {
System.out.println(num);
});
}
该方法很简单,就是遍历list参数并输出值,以该参数作为第一人称
,它是一个生产者
,因为它提供数据被程序读取。
但如果我们在遍历前往list里面添加一个Long类型的对象又会怎样呢?
大佬说,这太奇葩了,我的类型参数条件是 extends Number>
,明显,Long
是Number
的子类,为什么不让我把300L
加到集合里面呢?Java编译器的设计者脑袋被驴T了?
我们都知道,Java的泛型只在编译期间有效,编译后所谓的
,都会被替换成Object
。
它的作用是:
类型安全
,避免发生类似ClassCastException
的异常,把类型转换
错误的bug扼杀于编译过程
中。良好的可读性
,有了泛型,开发人员非常清晰知道,这个集合是存什么的,这个方法能接受怎样的参数。大佬稍安勿躁,先看下面的例子:
public void readData(java.util.List extends Number> list) {
list.add(300L);
list.forEach(num -> {
System.out.println(num);
});
}
我们假设,Java编译器不会对list.add(300L);
提示编译错误,那么假设我们使用如下代码调用readData
:
java.util.List list = new java.util.ArrayList<>();
list.add(100);
list.add(200);
readData(list);
这明显违反了保证类型安全
的初衷了,为什么呢?因为list
的类型参数是Integer,符合 extends Number>的条件。若list.add(300L);
不报错,则我们预期list里面的元素都是Integer的,但list.add(300L);
却往list里面插入了一个Long类型的元素,这已经违反了类型安全
的初衷了。
以上是我对泛型通配符的理解,若有不正确,希望大家指出,共同学习。