聊一聊泛型通配符
Java语言的泛型确实能给我们程序员带来很多便利,但是有一些泛型特性未必大家都掌握了。比如通配符?,上界通配符,下界通配符,它们的作用范围和应用场景等等。对于泛型通配符,其实普通业务型的程序员很少了解,但是对于研究性或者有阅读源码爱好的程序员想必早已掌握。从另外一个侧面也说明泛型通配符它的应用范围有限。本篇博文尽量把这个问题用通俗易懂的语言记录下来。
任意类型通配符 ?
?通配符:表示参数可以接收任意类型,但作用域是方法上面。
? 通配符,只能取得类中属性值,不能修改属性值,换句话说就是只能get值,不能set值,一旦set则会报错。因为类型不确定,无法设置确定类型。例如:
public void fun(Fruit<?> fruit) { //fruit.setName("Apple"); //错误,不能修改数据 System.out.println(fruit.getName()); }上界通配符
上界通配符<? extends T>表示泛型类是T类或者类的子类。只能get值,不能set值。也就是它会导致set方法失效。<? extends T>表示包括T在内的任何T的子类。可以用于类和方法。用于方法时,不能修改属性值,只能取得属性值,因为发生了父类(如Number)到子类的向下转型,需要强转,由于子类不确定,无法强转。
举个栗子:
<? extends Number>:用在Number类上,表示T只能是Number类或Number类的子类。
<? extends Number>:用在方法上,表示只能接受Number类及其子类的泛型类。
//作用在类上面,T只能是Number及其子类class Num<T extends Number> { private T num; public void setNum(T num){ this.num=num; } public T getNum(){ return this.num; }} public class Test { public static void fun(Num<? extends Number> num){ //此时?继承了Number类,但依旧不知道具体类型,即无法修改数据 //num.setNum(3);//Error System.out.println(num.getNum()); } public static void main(String[] args){ //Num<String> num1 = new Num<String>(); //Error,因为String不是Number的子类 Num<Integer> num2 = new Num<>(); num2.setNum(3); //自动装箱 fun(num2); //Num<? extends Number> num3 = new Num<>();//Error 因为编译器根本不知道?会是什么类型,那么num3.setNum(3.14);就会报错 Num<Double> num3 = new Num<>(); num3.setNum(3.14); fun(num3); }}下界通配符
下界通配符<? super T>表示取得泛型下限,表示泛型类只能是类和类的父类。只能set值,不能get值。也就是它会导致get方法失效,取出来的值只能放在Object对应的父类中。<? super T>表示包括T在内的任何T的父类。<? super T>只能用于方法,它既可以设置属性值,也可以取得属性值,因为子类到父类是自动的向上转型。
PECS原则
PECS原则英文全称是Producer Extends Consumer Super,它代表的含义是:频繁往外读取内容的,适合用上界通配符extends,频繁往里插入内容的,适合用下界通配符super。上下边界让Java不同泛型之间的转换更容易。但不要忘记,这样的转换也有一定的副作用,那就是容器的部分功能可能失效。
代码示例
我们针对生活常用的盘子装水果进行场景模拟。比如现实生活中盘子可以装任何水果和食物。但Java编译器就没那么智能,它是不允许把"装香蕉的盘子"转换成"装任何水果的盘子",我两不是一个娘生的。error incompatible types: Platecannot be converted to Plate,就这和人面向对象的思维有点差异。实际上,编译器思考的逻辑是这样的:香蕉是水果;2 装香蕉的盘子不是装水果的盘子。也就是说虽然你装的水果之间有继承关系(Banana和Fruit是is a的关系),但容器之间是没有继承关系的。所以不可以把Plate的引用传递给Plate。为了让泛型使用起来方便,就有了和的办法,来让"水果盘子"和"香蕉盘子"之间发生关系,这样用起来会爽YY,是不是。代码如下:
//食物class Food{}//水果class Fruit extends Food{}//香蕉class Banana extends Fruit{}// 容器盘子class Plate<T extends Fruit> { private T item; public Plate(T t) { item = t; } public void set(T t) { item = t; } public T get() { return item; }}public class FruitRunMain { public static void upperBoundWildcards() { Plate<? extends Fruit> p = new Plate<Banana >(new Banana ()); //不能存入任何元素 //p.set(new Fruit()); //Error //p.set(new Banana ()); //Error //读取出来的东西只能存放在Fruit或它的基类里。 Fruit newFruit1 = (Fruit) p.get(); Object newFruit2 = p.get(); Banana newFruit3 = p.get(); //Error Banana newFruit3 = (Banana )p.get(); //可以强制类型转换 } public static void lowerBoundWildcards() { Plate<? super Fruit> p = new Plate<Fruit>(new Banana ()); //存入元素正常 p.set(new Fruit()); p.set(new Banana ()); //读取出来的东西只能存放在Object类里。// Banana newFruit3 = p.get(); //Error// Fruit newFruit1 = p.get(); //Error Object newFruit2 = p.get(); } public static void main(String[] args) { upperBoundWildcards(); lowerBoundWildcards(); }}
参考资料
1 通配符(?/<? extends 类>/ <? super 类>)https://blog.csdn.net/sophia__yu/article/details/83755466
2 <? extends T>和<? super T>
https://www.cnblogs.com/drizzlewithwind/p/6100164.html
3 泛型中<? super T>和<? extends T>的区别
https://www.cnblogs.com/lucky_dai/p/5485421.html