泛型是多数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);
}
网友评论