泛型是多數(shù)Javaer說不出的痛,加入?yún)f(xié)變技術(shù),更是痛不欲生,本文以List,Date,Time為藍(lán)本說明問題。
類圖如下:

image.png
1. 針對(duì)API的調(diào)用層面:
例如提供如下兩個(gè)API:
void consume(List<? extends java.util.Date>)
void produce(List<? super Date>)
調(diào)用過程演示:
public static void main(String[] args) {
//符合List <? extends Date>的幾種聲明方法和實(shí)例化方法,都可以傳入consume方法
List<Time> list1=new ArrayList<>();//jdk7之后自動(dòng)推斷出Time,可以在ArrayList不使用泛型類型
var list1x=new ArrayList<Time>();//jdk10之后,如使用var聲明,則必須寫出Time類型
List<Date> list12=new ArrayList<>();
//List<Date> list12=new ArrayList<Time>();編譯錯(cuò),不能進(jìn)行協(xié)變(泛型無多態(tài))
List <? extends Date> list13=new LinkedList<Time>();//此處可以進(jìn)行的協(xié)變
List <? extends Time> list14=new Vector<>();//可對(duì)針對(duì)Date的子類進(jìn)行
//符合List <? super Date>的幾種聲明方法和實(shí)例化方法,都可以傳入produce方法
List<Comparable> list2=new ArrayList<>();
List<Date> list21=new ArrayList<>();
//List<Date> list22=new ArrayList<Object>();//編譯錯(cuò),不能進(jìn)行協(xié)變
List<? super Date> list22=new LinkedList<Object>();//進(jìn)行了向上的協(xié)變
List <? super Comparable> list23=new Vector<>();//可對(duì)針對(duì)Date的基類進(jìn)行
//由于存在"泛型變量?extends Date",可以進(jìn)行"向下協(xié)變",編譯通過
//在應(yīng)用上,我們可以傳入所有的以Date為基類"泛型List",但這僅限于進(jìn)行"消費(fèi)行為:從方法中返回對(duì)象"
//方法簽名:void consume(List<? extends java.util.Date)
cousume(list14);
//由于存在"泛型變量?super Date",可以進(jìn)行"向上協(xié)變",編譯通過
//在應(yīng)用上,我們可以傳入所有的以Date為子類的"泛型List",但這僅限于進(jìn)行"生產(chǎn)行為:向方法中傳入對(duì)象"
//方法簽名:void produce(List<? super Date>)
produce(list23);
}
2. 針對(duì)API的設(shè)計(jì):
public static void cousume(List<? extends Date> list /*向下協(xié)變*/){
/*
此時(shí)編譯器,只能預(yù)測(cè)list中的泛型類型是一個(gè)Date或子類型,但不知道是哪一個(gè)子類型,
所以此時(shí):
1. 任何從list取出數(shù)據(jù)進(jìn)行消費(fèi),只要按照Date類型都是安全的
原因:(1)Date及其子類型都具備Date公開的行為和屬性,所以按Date進(jìn)行消費(fèi)是安全的
(2)此時(shí)你也可以向下轉(zhuǎn)型到Date的子類型進(jìn)行消費(fèi),但需要自己承擔(dān)風(fēng)險(xiǎn)
2. 任何把對(duì)象交給list內(nèi)部進(jìn)行處理的行為,都是不安全的:
原因:
(1)在傳入Date類型對(duì)象,其真實(shí)類型對(duì)象是動(dòng)態(tài)綁定的,其類型有可能進(jìn)行了擴(kuò)展行為和屬性,編譯器無法預(yù)知
(2)此時(shí)Java的策略是從源頭杜絕錯(cuò)誤,編譯期報(bào)錯(cuò)
*/
//此處的list,只能按Date或基類進(jìn)行消費(fèi)
Date date=list.get(0);
Object d=list.get(0);
//Time t=list.get(0); // 編譯錯(cuò):只能消費(fèi)按"上界"消費(fèi)
/** 以下編譯錯(cuò):
* 任何向list中的生產(chǎn),都是禁止的
list.add(new Object());
list.add(new Date());
list.add(new Time());
**/
}
public static void produce(List<? super Date> list){
/*
此時(shí)編譯器可以確定list中的泛型類型只能是Date或它的基類,
此時(shí):任何的以Date類型或期子類型的生產(chǎn)型行為都是安全的(add方法內(nèi)部以Date類型完成算法邏輯)
*/
list.add(new Date());
list.add(new Time(12344));
//編譯不能過:
//list.add(new Object());
/*
由于"向上協(xié)變",list中可以存儲(chǔ)任何的Date的基類,編譯器無法預(yù)知,所以只能按照Object進(jìn)行消費(fèi)
*/
Object obj=list.get(0);
}