對逆變的概念可以參考本系列的前1篇文章: Scala之類型參數化:Type Parameterization 本文的重點是要解釋“逆變”的公道性。本文原文出處: http://blog.csdn.net/bluishglc/article/details/52585991 嚴禁任何情勢的轉載,否則將拜托CSDN官方保護權益!
在思考“逆變”的公道性這個問題上,我們需要清晰地認識到1個條件,即父類與子類之間的關系實質,我們說:如果類A是類B的父類,那末所有出現類A聲明的地方,我們都可使用類B的實例進行替換,或說所有適用于類A的操作一樣適用于類B,簡言之就是子類型可以透明無害地替換父類型(也就是里氏替換原則),由于子類型1定也是父類型,但父類型未必1定是子類型(有其他子類型),上述原則就是大家所熟知的里氏替換原則。
Liskov Substitution Principle (里氏替換原則)
It is safe to assume that a type T is a subtype of a type U if you can substitute a value of type T wherever a value of type U is required.
The principle holds if T supports the same operations as U and all of T’s operations require less and provide more than the corresponding operations in U.
在回顧完上述表述以后,我們來重新審視1下“逆變”存在的公道性。首先定義以下1個Animal類族:
scala> class Animal
defined class Animal
scala> class Bird extends Animal
defined class Bird
scala> class Dog extends Animal
defined class Dog
現在有f1,f2兩個函數:
def f1(x: Bird): Unit // instance of Function1[Bird, Unit]
def f2(x: Animal): Unit // instance of Function1[Animal, Unit]
在這里,f1是f2的父類。為何?我們知道,Function1的類型聲明是Function1[-T1,+R],即函數是雖參數類型逆變,返回值類型協變的。其中隨返回值類型協變是很容易理解的,隨參數類型逆變常常讓人費解,對此,我們一樣使用前面提到的原則進行判定:父類可以被子類替換,反之則不可以,但是這里的情況會略微有些復雜,由于我們要判斷的是函數類型之間的可替換關系(即父子關系),我們可以認為函數是1種“復合”類型,它們的類型是由它們的參數和返回值的類型決定的,因此我們可以很自然的延展出這樣1個規則:對具有相同參數列表類型和返回值類型的函數,如果傳給函數1的參數類型一樣可以傳給函數2,而傳給函數2的參數未必都能傳給函數1,也就是說,只從參數部份考量,函數1可以被函數2替換,即函數1是父類,函數2 是子類。
對f2,我們說傳給它1個Animal實例它可以工作,傳給它1個Bird實例它依然可以工作,在傳給它1個Bird實例時,我們就要注意到,這時候的f2(僅看參數部份)實例的類型實際上就已變成f1了,這時候所有聲明使用f1類型的地方都可以用f2的實例去替換,但是反過來,所有聲明了使用f2類型的地方我們是不能用f1的實例去替換的,由于對f2來講,它可以接受Animal類型的任何其他子類型,比如Dog,但是Dog類型明顯不適用于f1的。所以總結起來,f1可以被f2替換,但是f2不能被f1替換,所以f1是f2的父類型!
讓我們再延伸地思考1下,我們可以說:由于f2是“消費”(consume)1個較為“通用”的父類型,這使得函數f2本身自然地能接納和處理給定參數類型的所有子類型,也就意味著f2可以去替換或賦值給那些所有聲明使用“具體”子類型為參數的函數,比如f1, 所以f1是父類,f2是子類!這類“消費”關系決定了逆變存在的理由,可以表述為PECS原理:
PECS stands for producer-extends, consumer-super.
In other words, if a parameterized type represents a T producer, use <? extends T>;
if it represents a T consumer, use <? super T>.
上述PECS原則換1種方法表述為:
G[+A]類似1個生產者,提供數據。(大部份情況下稱G為容器類型)
G[-A] 是1個消費者,主要用來消費數據。(參考垃圾桶和垃圾的例子)
雖然我們仍然在使用里氏替換原則來分析和辨認“逆變”的場景,但是我們不能不承認這類解釋仍然只是1種邏輯上的逆推,它的解釋總是讓人覺得不是那末“解癢”,在本文的最后,我試圖從正面給出1種“逆變”公道性的解釋:
**我們說在現實世界里,如果有1類物品專門針對另外一類物品而存在,除人們1般認為的伴隨著被處理物品的細化,處理品本身需要不斷地跟進細化,這是“協變”的場景,也確切有可能會存在另外1種完全相反的情形:即伴隨著被處理物品的細化,在掌握了愈來愈多處被理物品的信息和特點的趨勢下,處理物品本身卻可以變的愈發的簡單(處理面變窄),反倒是那些處理更通用物品的處理類復雜的多,由于它們要斟酌的可能的情況更多更復雜,那末這類情形就是典型的“逆變”!
1個典型是例子是空調和遙控器,如果說遙控器是基于空調類型的范型類,那末它天然應當是逆變的,即:RemoteController[-T], 空調品牌和型號越細化,遙控器實際上越單1,實現起來也越簡單,反倒是隨著空調類型不斷地向上抽象,遙控器會變得越加復雜,直到面向所有空調通用的遙控器RemoteController[AirConditioner]誕生,這也就是我們見到過的那種萬能遙控器。萬能遙控器可以替換任何品牌和型號的遙控器,因此它是它們的子類!
我們可以看到大多數的逆變類有以下1些特點: