引言
類型參數(shù)化(Parameterized Types)可以用來編寫泛型類和特質(zhì)谷市,比如定義Set[T]案站,這使得我們可以創(chuàng)建諸如Set[String]的類型注益。而變化型注解(Variance Annotation)定義了參數(shù)化類型的繼承關(guān)系带斑,比如Set[String]是Set[AnyRef]的子類型模捂。
這些語法可以讓我們實(shí)現(xiàn)信息隱藏技術(shù),同時它們也是編寫庫程序的基礎(chǔ)贤壁。
類型參數(shù)化
這里以水果盒的代碼作為例子:
abstract class Fruit {
def name: String
}
class Orange extends Fruit {
def name = "orange"
}
class Apple extends Fruit {
def name = "apple"
}
abstract class Box {
def fruit: Fruit
def contains(aFruit: Fruit) =
fruit.name.equals(aFruit.name)
}
class OrangeBox(orange: Orange) extends Box {
def fruit: Orange = orange
}
class AppleBox(apple: Apple) extends Box {
def fruit: Apple = apple
}
上面的例子悼枢,定義了Box的兩個子類,OrangeBox和AppleBox脾拆,這考慮的類型安全馒索,因?yàn)閒ruit方法的返回值受到了Orange和Apple類型的特別限制。這也同樣帶來了維護(hù)代碼量和類型安全的矛盾名船。
基于這個問題绰上,Scala允許使用參數(shù)化類型,即你可以使用類型參數(shù)來代替實(shí)際的類型渠驼。類型參數(shù)可以認(rèn)為是一個限制類型的別名蜈块。
根據(jù)類型繼承關(guān)系,Scala編譯器允許AppleBox或者OrangeBox的實(shí)例賦值給Box類型變量,在Box子類中fruit方法的實(shí)現(xiàn)返回了Apple或者Orange類型百揭,這同樣可以賦值給Fruit類型的變量爽哎。
變化型注解:
- 假設(shè)聲明了
class Orange extends Fruit
,而后class Box[A]
中的A可以有前綴+或-器一。
- A课锌,沒有任何注解,是不變型祈秕。該狀態(tài)下渺贤,
Box[Orange]
和Box[Fruit]
沒有任何繼承關(guān)系 - +A,是協(xié)變類型请毛。此時癣亚,
Box[Orange]
是Box[Fruit]
的子類型,并且變量聲明val f: Box[Fruit] = new Box[Orange]()
是允許的获印。 - -A述雾,是逆變類型。此時兼丰,
Box[Fruit]
是Box[Orange]
的子類型玻孟,并且變量聲明val f: Box[Orange] = new Box[Fruit]()
是允許的。
從總體來看鳍征,變化類型參數(shù)可以被認(rèn)為是擴(kuò)展泛型編程的類型檢查范圍的工具黍翎。它提供了額外的類型安全,即保證類型安全的前提下艳丛,為開發(fā)者提供了利用類型層級的可能性匣掸。
子類型多態(tài)
協(xié)變和逆變注解使得通過類型參數(shù)的繼承關(guān)系來推斷泛化類型的繼承關(guān)系,利用該注解可以限制類型參數(shù)在泛型類中一些可能的使用氮双,Scala編譯器針對不恰當(dāng)?shù)氖褂眠M(jìn)行檢查和報錯碰酝。
abstract class Box[+F <: Fruit] {
def fruit: F
def contains(aFruit: Fruit) = fruit.name.equals(aFruit.name)
}
class OrangeBox(orange: Orange) extends Box[Orange] {
def fruit = orange
}
class AppleBox(apple: Apple) extends Box[Apple] {
def fruit = apple
}
var fruitBox: Box[Fruit] = new AppleBox(new Apple)
var fruit: Fruit = fruitBox.fruit
上面的例子中,Box[+F]類型參數(shù)是協(xié)變的戴差,那么將Box[Apple]賦值給Box[Fruit]類型變量fruitBox是合法的送爸。類型變量F用在函數(shù)返回類型中是合理的(比如Box.fruit),在fruitBox.fruit方法的調(diào)用中暖释,保證返回給Fruit類的實(shí)例是一個更加具體的類型袭厂,由于類型參數(shù)是協(xié)變的,需要返回一個比Fruit類型更加具象的類型球匕。
協(xié)變和逆變點(diǎn)
函數(shù)在參數(shù)上是逆變的纹磺,在返回值上則是協(xié)變的。通常而言亮曹,對于某個對象消費(fèi)的值適用逆變橄杨,而對于它產(chǎn)出的值適用協(xié)變秘症。
如果一個對象同時消費(fèi)和產(chǎn)出某值,則類型應(yīng)該保持不變讥珍。這通常適用于可變數(shù)據(jù)結(jié)構(gòu),比如標(biāo)準(zhǔn)庫中的Array窄瘟、ArrayBuffer衷佃、ListBuffer等。
Scala編譯器檢查變化型注解的機(jī)制
為了核實(shí)變化型注解的正確性蹄葱,Scala編譯器會把類或特質(zhì)中可能用到類型參數(shù)的地方分類為正氏义,負(fù)或中立。注解了+號的類型參數(shù)只能被用在正的位置上图云,而注解了-號的類型參數(shù)只能用在負(fù)的位置上惯悠。沒有變化型注解的類型參數(shù)可以用于任何位置。
Scala編譯器在檢查泛型類時竣况,會跟蹤所有使用類型參數(shù)的位置克婶,然后根據(jù)代碼中變化型的位置來決定正確與否。如果類型參數(shù)出現(xiàn)在了禁止的位置丹泉,編譯器將會報錯情萤。詳細(xì)的檢查規(guī)則可以參見這里。
為了對用到類型參數(shù)的地方進(jìn)行分類摹恨,編譯器首先從類型參數(shù)的聲明開始筋岛,然后進(jìn)入更深的內(nèi)嵌層。
處于聲明類的最頂層被劃為正晒哄,默認(rèn)情況下睁宰,更深的內(nèi)嵌層的地方的分類會與它外層一致,但仍有幾種特殊情況可以使得類型參數(shù)的類型發(fā)生翻轉(zhuǎn):
- 方法的值參數(shù)位置的參數(shù)類型
- 方法的類型參數(shù)子句位置的參數(shù)類型
- 類型參數(shù)的下界的下界參數(shù)類型
- 參數(shù)化類型的類型參數(shù)寝凌,當(dāng)類型參數(shù)是逆變時
類型的類型參數(shù)位置柒傻,比如C[Arg]的Arg,也有可能被翻轉(zhuǎn)较木,如果C的類型參數(shù)標(biāo)注了+诅愚,那么類別不變;如果C的類型參數(shù)標(biāo)注了-劫映,那么當(dāng)前類別被翻轉(zhuǎn)违孝。
舉例解釋:
-
def method(parameter: T)
中,參數(shù)T在逆變的位置泳赋,遵循第一條規(guī)則 -
def method[U <: T]
中雌桑,參數(shù)T在逆變的位置,遵循第二條規(guī)則 -
def method[U >: T]
中祖今,參數(shù)T在協(xié)變的位置校坑,循序第二條和第三條規(guī)則 -
class Box[-A]
中拣技,Box被聲明為逆變,此情況下耍目,def method(parameter: Box[T])
中的T應(yīng)該是協(xié)變的位置膏斤,遵循第一條和第四條規(guī)則
下界
在一個方法的類型參數(shù)子句中的下界參數(shù),為何從逆變位置翻轉(zhuǎn)成協(xié)變邪驮?
Box[+T]中的協(xié)變類型參數(shù)T莫辨,根據(jù)子類型多態(tài)性,只能變得更加具象毅访,也就是說沮榜,受到T的限制,其實(shí)類型將沿著類型層級往下走喻粹。如果將T作為下界蟆融,使用U >: T
中的U來作為類型約束,則是可行的守呜。
class Box[+T](fruit: T) {
def method[U >: T](p: U) = { new Box[U](p) }
}
var apple: Apple = new Apple
var box: Box[Fruit] = new Box[Orange](new Orange)
box = box.method(apple)
這個例子中型酥,即使box在實(shí)例化的時候復(fù)制為一個子類型Box[Orange],Box.method的調(diào)用仍然是合法的查乒。
通過語法U >: T
冕末,定義了T為U的下界。結(jié)果侣颂,U必須是T的超類型档桃,method的參數(shù)也變?yōu)轭愋蚒而不是類型T,而方法返回值也變成了Box[U]憔晒,取代了Box[T]藻肄。
通過把Apple對象加入到Box[Orange]中,返回結(jié)果為Box[Fruit]類型拒担。
合法的變化型位置
下面的幾種變化型參數(shù)均為合法的語法格式:
abstract class Box[+A]{ def foo(): A }
abstract class Box[-A]{ def foo(a: A) }
abstract class Box[+A]{ def foo[B >: A](b: B) }
abstract class Box[-A]{ def foo[B <: A](): B}
參考資料
The Scala Type System: Parameterized Types and Variances
轉(zhuǎn)載請注明作者Jason Ding及其出處
Github博客主頁(http://jasonding1354.github.io/)
GitCafe博客主頁(http://jasonding1354.gitcafe.io/)
CSDN博客(http://blog.csdn.net/jasonding1354)
簡書主頁(http://www.reibang.com/users/2bd9b48f6ea8/latest_articles)
Google搜索jasonding1354進(jìn)入我的博客主頁