java泛型的“协变”技术

2022-04-17  本文已影响0人  JohnYuCN

泛型是多数Javaer说不出的痛,加入协变技术,更是痛不欲生,本文以List,Date,Time为蓝本说明问题。

类图如下:


image.png

1. 针对API的调用层面:

例如提供如下两个API:

void consume(List<? extends java.util.Date>)
void produce(List<? super Date>)

调用过程演示:

    public static void main(String[] args) {
        //符合List <? extends Date>的几种声明方法和实例化方法,都可以传入consume方法
        List<Time> list1=new ArrayList<>();//jdk7之后自动推断出Time,可以在ArrayList不使用泛型类型
        var list1x=new ArrayList<Time>();//jdk10之后,如使用var声明,则必须写出Time类型
        List<Date> list12=new ArrayList<>();
        //List<Date> list12=new ArrayList<Time>();编译错,不能进行协变(泛型无多态)
        List <? extends Date> list13=new LinkedList<Time>();//此处可以进行的协变
        List <? extends Time> list14=new Vector<>();//可对针对Date的子类进行


        //符合List <? super Date>的几种声明方法和实例化方法,都可以传入produce方法
        List<Comparable> list2=new ArrayList<>();
        List<Date> list21=new ArrayList<>();
        //List<Date> list22=new ArrayList<Object>();//编译错,不能进行协变
        List<? super Date> list22=new LinkedList<Object>();//进行了向上的协变
        List <? super Comparable> list23=new Vector<>();//可对针对Date的基类进行


        //由于存在"泛型变量?extends Date",可以进行"向下协变",编译通过
        //在应用上,我们可以传入所有的以Date为基类"泛型List",但这仅限于进行"消费行为:从方法中返回对象"
        //方法签名:void consume(List<? extends java.util.Date)
        cousume(list14);

        //由于存在"泛型变量?super Date",可以进行"向上协变",编译通过
        //在应用上,我们可以传入所有的以Date为子类的"泛型List",但这仅限于进行"生产行为:向方法中传入对象"
        //方法签名:void produce(List<? super Date>)
        produce(list23);
    }

2. 针对API的设计:

  
    public static void cousume(List<? extends Date> list /*向下协变*/){
        /*
        此时编译器,只能预测list中的泛型类型是一个Date或子类型,但不知道是哪一个子类型,
        所以此时:
        1. 任何从list取出数据进行消费,只要按照Date类型都是安全的
            原因:(1)Date及其子类型都具备Date公开的行为和属性,所以按Date进行消费是安全的
                 (2)此时你也可以向下转型到Date的子类型进行消费,但需要自己承担风险
        2. 任何把对象交给list内部进行处理的行为,都是不安全的:
            原因:
            (1)在传入Date类型对象,其真实类型对象是动态绑定的,其类型有可能进行了扩展行为和属性,编译器无法预知
            (2)此时Java的策略是从源头杜绝错误,编译期报错
         */

        //此处的list,只能按Date或基类进行消费
        Date date=list.get(0);
        Object d=list.get(0);
        //Time t=list.get(0); // 编译错:只能消费按"上界"消费

        /** 以下编译错:
         * 任何向list中的生产,都是禁止的
        list.add(new Object());
        list.add(new Date());
        list.add(new Time());
         **/
    }

    public static void produce(List<? super Date> list){
        /*
        此时编译器可以确定list中的泛型类型只能是Date或它的基类,
        此时:任何的以Date类型或期子类型的生产型行为都是安全的(add方法内部以Date类型完成算法逻辑)
         */
        list.add(new Date());
        list.add(new Time(12344));
        //编译不能过:
        //list.add(new Object());

        /*
        由于"向上协变",list中可以存储任何的Date的基类,编译器无法预知,所以只能按照Object进行消费
         */
        Object obj=list.get(0);

    }


上一篇下一篇

猜你喜欢

热点阅读