我們之前往往都比較關注類型本身,卻常常忽略類型轉換的性質。最近在拜讀《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是無關的
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