讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議97:警惕泛型是不能協變和逆變的 >

建議97:警惕泛型是不能協變和逆變的

什麼叫協變(covariance)和逆變(contravariance)?Wiki上是這樣定義的:

Within the type system of a programming language, covariance and contravariance refers to the ordering of types from narrower to wider and their interchangeability or equivalence in certain situations(such as parameters, generics, and return types).

在編程語言的類型框架中,協變和逆變是指寬類型和窄類型在某種情況下(如參數、泛型、返回值)替換或交換的特性,簡單地說,協變是用一個窄類型替換寬類型,而逆變則是用寬類型覆蓋窄類型。其實,在Java中協變和逆變我們已經用了很久了,只是我們沒發覺而已,看如下代碼:


class Base{

public Number doStuff(){

return 0;

}

}

class Sub extends Base{

@Override

public Integer doStuff(){

return 0;

}

}


子類的doStuff方法返回值的類型比父類方法要窄,此時doStuff方法就是一個協變方法,同時根據Java的覆寫定義來看,這又屬於覆寫。那逆變是怎麼回事呢?代碼如下:


class Base{

public void doStuff(Integer i){

}

}

class Sub extends Base{

public void doStuff(Number n){

}

}


子類的doStuff方法的參數類型比父類要寬,此時就是一個逆變方法,子類擴大了父類方法的輸入參數,但根據覆寫定義來看,doStuff不屬於覆寫,只是重載而已。由於此時的doStuff方法已經與父類沒有任何關係了,只是子類獨立擴展出的一個行為,所以是否聲明為doStuff方法名意義不大,逆變已經不具有特別的意義了,我們來重點關注一下協變,先看如下代碼是否是協變:


public static void main(Stringargs){

Base base=new Sub();

}


base變量是否發生了協變?是的,發生了協變,base變量是Base類型,它是父類,而其賦值卻是子類實例,也就是用窄類型覆蓋了寬類型。這也叫多態(Polymorphism),兩者同含義,在Java世界裡「重複發明」輪子的事情多了去了。

說了這麼多,下面再來想想泛型是否也支持協變和逆變,答案是:泛型即不支持協變,也不支持逆變。很受傷是吧?為什麼會不支持呢?

(1)泛型不支持協變

數組和泛型很相似,一個是中括號,一個是尖括號,那我們就以數組為參照對象,看如下代碼:


public static void main(Stringargs){

//數組支持協變

Numbern=new Integer[10];

//編譯不通過,泛型不支持協變

List<Number>ln=new ArrayList<Integer>();

}


ArrayList是List的子類型,Integer是Number的子類型,裡氏替換原則(Liskov Substitution Principle)在此處行不通了,原因就是Java為了保證運行期的安全性,必須保證泛型參數類型是固定的,所以它不允許一個泛型參數可以同時包含兩種類型,即使是父子類關係也不行。

泛型不支持協變,但可以使用通配符(Wildcard)模擬協變,代碼如下所示:


//Number的子類型(包括Number類型)都可以是泛型參數類型

List<?extends Number>ln=new ArrayList<Integer>();


"?extends Number"表示的意思是,允許Number所有的子類(包括自身)作為泛型參數類型,但在運行期只能是一個具體類型,或者是Integer類型,或者是Double類型,或者是Number類型,也就是說通配符只是在編碼期有效,運行期則必須是一個確定類型。

(2)泛型不支持逆變

Java雖然可以允許逆變存在,但在對類型賦值上是不允許逆變的,你不能把一個父類實例對像賦值給一個子類類型變量,泛型自然也不允許此種情況發生了,但是它可以使用super關鍵字來模擬實現,代碼如下。


//Integer的父類型(包括Integer)都可以是泛型參數類型

List<?super Integer>li=new ArrayList<Number>();


"?super Integer"的意思是可以把所有Integer父類型(自身、父類或接口)作為泛型參數,這裡看著就像是把一個Number類型的ArrayList賦值給了Integer類型的List,其外觀類似於使用一個寬類型覆蓋一個窄類型,它模擬了逆變的實現。

泛型既不支持協變也不支持逆變,帶有泛型參數的子類型定義與我們經常使用的類類型也不相同,其基本的類型關係如表7-1所示。

注意 Java的泛型是不支持協變和逆變的,只是能夠實現協變和逆變。