Java 協變性 逆變性 學習筆記

在面向對象的計算機程序語言中,經常涉及到類型之間的轉換,例如從具體類小貓到動物之間的類型轉換(上行轉換),或者從形狀向三角形之間的轉換(下行轉換)。
我們之前往往都比較關注類型本身,卻常常忽略類型轉換的性質。最近在拜讀《thinking in java》看到一些關於類型轉換性質的比較抽象理論的內容,仔細研究一下,分享給大家。
我們將圍繞如下三個核心名詞:協變性(covariance)、逆變性(contravariance)和無關性(invariant)。他們都是用來描述類型轉換的性質的術語,形式化的描述如下:

如果A和B是類型,f表示類型轉換,≤表示子類型關係,(例如A≤B,表示A是B的子類)那麼:
如果A≤B 則f(A) ≤ f(B) 那麼 f是協變的
如果A≤B 則f(B) ≤ f(A) 那麼 f 是逆變的

如果上面兩種都不成立,那麼f是無關的


例如java中,f(A) = List<A> ,這裏List聲明如下:
class List<T>{…}
我們可以理解泛型類List輸入一個類型參數T,然後將T轉換成類型List<T>,這個過程本身是一個類型轉換f。
我們舉例A = Object,B=String,經過類型變換f以後f(A)=List<Object>,f(B) = List<String>。
String ≤ Object 但是 f(A) ≤ f(b)卻不成立。
所以上面的類型轉換是無關的。

在Java語言中,有一些語法可以用協變性和逆變性來理解。
數組
例如 A = String, B = Object 我們這裏有A≤B
f(A) = String[], f(B) = Object[]. 因爲Object[] objects = new String[n]. 所以我們可以認爲數組具有協變性。

X = Y


1. 協變返回值


在面嚮對象語言中,一個協變返回值方法是一個在子類覆蓋該方法的時候,方法的返回值可以被一個“更窄”的類型所替代。
(C#並不支持這個技術,C++和Java JDK5.0後開始支持)
例如:
public static class Super {
      Object getSomething() {
      return null;
   }
}
public static class SubClass extends Super {
@Override
     String getSomething() {
           return null;
     }
}

這段話主要的思想就是協同返回值使用在子類需要一個不同於父類覆蓋方法的返回值,但是返回值卻有繼承關係的情況下。
一個可能更好的例子如下所示:
class Collection {
     Iterator iterator() { ... }
}

class List extends Collection {
    @Override 
    ListIterator iterator() { ... }
} 
Iterator函數獲得到當前集合的迭代器,在子類中,迭代器有着更確切的表示,所以使用了Iterator的子類ListIterator作爲新的返回值。

2. 逆變參數

先舉個例子,可以一下子明白逆變參數的例子,其實逆變參數使用環境依然是在繼承體系下的函數覆蓋。
class Super {
          void doSomething(String parameter) {
          }
}

class Sub extends Super {
        void doSomething(Object parameter) {
         }
}

爲了更好的理解這個問題,首先引入Liskov Substitution Principle的概念。這個原則的內容就是說,子類的對象可以在任何需要父類對象的地方使用。
這個原則和參數以及返回值有什麼關係呢?
假如我們定義類如下:
class Super {
       ReturnType doSomething(ParameterType a) {
            return null;
       }
}

class Sub extends Super {
       ReturnType doSomething(ParameterType a) {
                return null;
       }
}


假如我有Super sup;
首先考慮參數,現在要調用sup.doSomething(o);
如果兩個函數需要的參數類型一致,沒有什麼值得討論的。假如在一些情況下,sub需要的參數類型變成了和ParameterType有繼承關係的另一個類NewParameterType。如下:
class Sub extends Super {
          ReturnType doSomething(NewParameterType a) {
                return null;
          }
}


如果NewParameterType是ParameterType的子類,那麼sup.doSomething(o)調用中,如果o是指向NewParameterType的對象,則不會被通過。只有NewParameterType是ParameterType的父類的時候,纔不會發生任何問題。所以在提出逆變參數的概念。
同樣地,利用這個原則也可以用來理解。
假如在某使用環境中有如下語句:
ReturnType rt = sup.doSomething(o);
假如設計變更,子類需要返回一個與ReturnType有繼承關係的類NewReturnType,如果返回的是ReturnType的父類,那麼則上面語句則不能通過編譯,只有NewReturnType是ReturnType的子類的時候,纔不會違反Liskov子類原則。

Java泛型
在Java泛型中,例如
class DataHolder<T>{

}
假如類型 A ≤ B, 但是直接使用DataHolder<A> 和DataHolder<B>是不可變的,之前我們已經敘述過。
但是利用java提供的通配符語法,卻可以提供一個協變的類型轉換。
DataHolder<A> ≤DataHolder<? Extends B>

例如:
static boolean find(Iterable<? extends Object> where, Object what){
        return false;
}
可以使用Iterable<String> 來調用find函數。


參考資料:

http://stackoverflow.com/questions/2501023/demonstrate-covariance-and-contravariance-in-java

http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)

http://www.cnblogs.com/pugang/archive/2011/11/09/2242380.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章