版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/zwvista/article/details/78437667
協變,逆變與不變
能在使用父類型的場景中改用子類型的被稱爲協變。
能在使用子類型的場景中改用父類型的被稱爲逆變。
不能做到以上兩點的被稱爲不變。
以上的場景通常包括數組,繼承和泛型。
或者說:
任何能用父類做爲輸入參數的地方,當然也能用子類作爲替換,這叫逆變(Contravariant)。
任何返回子類的地方,當然也能安全的向上轉行爲父類。這叫協變(Covariant)。
舉例說明如下:
1)對於輸入參數(in):任何可以輸入父類animal的地方,當然可以輸入子類dog。
2)對於輸出參數(out):輸出dog的地方,客戶端僅僅把它看作animal來對待。
協變逆變與泛型(C#,Java)
在C#中,泛型參數的類型缺省是不變的,但是我們可以在定義泛型接口或委託時通過給參數類型加上out或in來標註該參數類型是協變還是逆變。
協變意味着你能把 IEnumerable<string> 用在需要 IEnumerable<object> 的地方。
這裏 IEnumerable<out T> 是協變,使用 out 標註。使用 out 標註的原因是協變參數一般處於輸出(output)者的地位,只讀不寫(read,get)。
逆變意味着你能把 IComparable<object> 用在需要 IComparable<string> 的地方。
這裏 IComparable<in T> 是逆變,使用 in 標註。使用 in 標註的原因是逆變一般處於輸入(input)者的地位,只寫不讀(write,put)。
Covariance and contravariance real world example
在Java中,泛型參數的類型是不變的。但是我們可以在使用泛型類或接口時在參數類型的位置上使用通配符加上extends或super來指定該參數類型是協變還是逆變。
List<? extends T> 是協變(參數類型只能使用T及其子類型,T是上限),用於生產者(指函數的入口參數),只讀不寫。
List<? super T> 是逆變(參數類型只能使用T及其父類型,T是下限),用於消費者(指函數的出口參數或返回值),只寫不讀。
Difference between <? super T> and <? extends T> in Java
泛型中協變逆變的類型安全性
協變泛型參數是生產者,處於輸出者地位,只讀不寫。
協變泛型參數使用子類型代替父類型是類型安全的,這是因爲在讀取時子類對象可以被看做父類對象。
逆變泛型參數是消費者,處於輸入者地位,只寫不讀。
逆變泛型參數使用父類型代替子類型是類型安全的。這是因爲在寫入時子類對象可以被安全的轉換成父類對象。
協變泛型參數類型 逆變泛型參數類型
入口 函數 出口
輸出者 輸入者
子類 => 父類 ======> 子類 <= 父類
public class Collections {
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++)
dest.set(i, src.get(i));
}
}
協變與數組(C#,Java)
在C#和Java中,數組都是協變的。子類數組是父類數組的子類型,因而在調用參數是父類數組的函數時能夠傳入子類數組。
在C#中,string[] 是 object[] 的子類型。
在Java中,String[] 是 Object[] 的子類型。
協變數組類型是類型不安全的,處理不當會拋出異常。
協變數組類型雖然類型不安全,但是數組類型能夠協變是一個合理的選擇。
這是因爲早期C#和Java都缺乏泛型,如果數組不允許協變,將無法使用多態。
Why are arrays covariant but generics are invariant?
協變逆變與繼承
C++和Java支持協變返回值類型(covariant return type),也就是允許基類虛函數(方法)在子類中被覆蓋(重寫)時,子類中相應虛函數(方法)的返回值類型不同於基類虛函數(方法)的返回值類型,條件是前者是後者的子類型。C#不支持這一特性。
class VehicleFactory {
public:
virtual Vehicle * create() const { return new Vehicle(); }
virtual ~VehicleFactory() {}
};
class CarFactory : public VehicleFactory {
public:
virtual Car * create() const override { return new Car(); } // 協變返回值類型
// 子類虛函數create的返回值類型Car *是基類虛函數create的返回值類型Vehicle *的子類型。
};
某些語言支持逆變(協變)參數類型(contravariant/covariant argument type),也就是允許基類虛函數(方法)在子類中被覆蓋(重寫)時,子類中相應虛函數(方法)的參數類型不同於基類虛函數(方法)的參數類型,條件是前者是後者的父(子)類型。C++,C#以及Java均不支持逆變或協變參數類型。
繼承中協變逆變的類型安全性
在子類中使用子類型返回值類型代替父類中的父類型返回值類型(協變返回值類型)是類型安全的。
在子類中使用父類型參數類型代替父類中的子類型參數類型(逆變參數類型)是類型安全的。
在子類中使用子類型參數類型代替父類中的父類型參數類型(協變參數類型)是類型不安全的。
逆變參數類型 協變返回值類型
參數 函數 返回值
父類 => 子類 ======> 父類 <= 子類
協變逆變,父子類型關係以及類型轉換(轉型)
協變意味着能在使用父類型的場景中改用子類型,也就是在新場景中使用子類型後仍然得到子類型。
逆變意味着能在使用子類型的場景中改用父類型,也就是在新場景中使用父類型後卻能得到子類型。
下面改用符號語言:
S是T的子類型(S能夠被安全的轉型成T),記作 S <: T。
協變:在新場景 F 中,父子類型的關係被維持,即 F(S) <: F(T)。
逆變:在新場景 F 中,父子類型的關係被顛覆,即 F(T) <: F(S)。
不變:在新場景 F 中,父子類型的關係不明。
協變逆變與函數類型
函數 f 的參數類型爲A,返回值類型爲B,記作 A -> B。
協變:B <: B’ => A -> B <: A -> B’(協變返回值類型)
逆變:A <: A’ => A’ -> B <: A -> B(逆變參數類型)
函數型語言中函數類型通常滿足協變返回值類型和逆變參數類型。
Can parameters be contra- or covariant in Python?
協變逆變與C++
在C++中,指針和引用都是協變的,也就是在使用父類指針和引用的場景中能夠安全的使用子類指針和引用。
這是因爲子類指針和引用是父類指針和引用的子類型,語法上前者能夠被安全地隱式地轉換成後者,
在C++中,模板的參數類型是不變的。但是標準庫的某些類型支持協變和逆變。
shared_ptr類型支持協變,也就是shared_ptr<子類>是shared_ptr<父類>的子類型。
unique_ptr類型支持協變,也就是unique_ptr<子類>是unique_ptr<父類>的子類型。
以上兩者是協變的原因是以上兩者都是智能指針,需要模仿原生指針的特性。
function的類型參數中的返回值類型支持協變,也就是function<子類*(T)>是function<父類*(T)>的子類型。
function的類型參數中的參數類型支持逆變,也就是function<U(父類*)>是function<U(子類*)>的子類型。
#include <iostream>
#include <memory>
#include <functional>
using namespace std;
struct Base {};
struct Derived : Base {};
int main()
{
shared_ptr<Derived> p = nullptr;
shared_ptr<Base> p2 = p; // shared_ptr類型支持協變
function<Derived*(Base*)> f = nullptr;
function<Base*(Derived*)> f2 = f; // function的函數參數類型支持逆變,function的函數返回值類型支持協變
}
Covariance and Contravariance in C++ Standard Library
————————————————
版權聲明:本文爲CSDN博主「zwvista」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/zwvista/article/details/78437667