聊一聊Java泛型通配符

186 阅读5分钟
mp.weixin.qq.com/s?src=11&ti…


聊一聊泛型通配符

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