scala協變逆變上界下界---理解篇
Scala的協變和逆變上界與下界
1. 引子:
為了弄懂scala中協變和逆變這兩個概念,查閱了不少資料,但是還是要自己總結一下,會記得比較深刻。
那就從java和scala的對比說起吧。
java中:
如果你很理解java的泛型,就會知道:比如給定一個類B,和他的父類A。
那麼用多型, A a = new B 編譯器是允許的。
但是如果泛型B的集合直接賦給父類A的集合。List<A> aList = new ArrayList<B>();
舉個簡單的例子:
編譯不通過,編譯器提示:Object s = "abc"; List<Object> objects = new ArrayList<String>();
Type mismatch: cannot convert from ArrayList<String> to List<Object>
scala中:
我們知道sting是AnyRef的子類。直接賦值是可以的。 如果是將string的集合賦值給AnyRef的集合,在scala中也是可以的。原因就在於List其實是支援協變的。scala> val s:AnyRef = "abc" s: AnyRef = abc scala> var objects : List[AnyRef] = List[String]("abc","123") objects: List[AnyRef] = List(abc, 123)
2. 協變
[+T], covariant (or “flexible”) in its type parameter T,類似Java中的(? extends T), 即可以用T和T的子類來替換T,里氏替換原則。
可以看到List的定義:
type List[+A] = scala.collection.immutable.List[A]
協變的符號是[+A],意味著支援泛型A的子類集合向A進行賦值。
在這個例子裡是List的是支援協變的。
3.不變
[T], invariant in its type parameter T
在scala可變集合中,MutableList是一個不變的型別。
定義:
class MutableList[A]
extends AbstractSeq[A]
......
我們還用相似的場景,將一個MutableList的string型別付給MutableList的AnyRef型別,這樣的賦值是不允許的。
編譯器會提示class MutableList is invariant in type A. 即在型別A中MutableList是不支援協變和逆變的。
scala> import scala.collection.mutable._
import scala.collection.mutable._
scala> val a : MutableList[AnyRef] = MutableList[String]("abc")
<console>:10: error: type mismatch;
found : scala.collection.mutable.MutableList[String]
required: scala.collection.mutable.MutableList[AnyRef]
Note: String <: AnyRef, but class MutableList is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: AnyRef`. (SLS 3.2.10)
val a : MutableList[AnyRef] = MutableList[String]("abc")
^
4. 逆變
[-T], contravariant, 類似(? supers T)
if T is a subtype of type S, this would imply that Queue[S] is a subtype of Queue[T]
只能用T的父類來替換T。是逆里氏替換原則。
在 scala.actors.OutputChannel 這個trait是一個逆變的型別。
trait OutputChannel[-Msg] {
......
對於OutputChannel[String], 支援的操作就是輸出一個string, 同樣OutputChannel[AnyRef]也一定可以支援輸出一個string, 因為它支援輸出任意一個AnyRef(它要求的比OutputChannel[String]少) 。
但反過來就不行, OutputChannel[String]只能輸出String, 顯然不能替換OutputChannel[AnyRef]
5.上界與下屆
Scala的上界和下界比較難理解, 因為和Java裡面的界不是一個意思...
Java中, (? extends T), T稱為上界, 比較容易理解, 代表T和T的子類, (? supers T), T稱為下界
Scala中, 界卻用於泛型類中的方法的引數型別上, 如下面的例子,
對於Queue中的append的型別引數直接寫T, 會報錯 (error: covariant type T occurs in contravariant position in type T of value x)
這個地方比較複雜, 簡單的說就是Scala內部實現是, 把類中的每個可以放型別的地方都做了分類(+, –, 中立), 具體分類規則不說了
對於這裡最外層類[+T]是協變, 但是到了方法的型別引數時, 該位置發生了翻轉, 成為-逆變的位置, 所以你把T給他, 就會報錯說你把一個協變型別放到了一個逆變的位置上
所以這裡的處理的方法就是, 他要逆變, 就給他個逆變, 使用[U >: T], 其中T為下界, 表示T或T的超類, 這樣Scala編譯器就不報錯了
class Queue[+T] (private val leading: List[T], private val trailing: List[T] ) { def append[U >: T](x: U) = new Queue[U](leading, x :: trailing) //使用T的超類U來替換T }
同樣對於上界也是,
trait OutputChannel[-T] { def write [U<:T] (x: U) //使用T的子類U來替換T }
總結:
1. 協變
[+T], covariant (or “flexible”) in its type parameter T,類似Java中的(? extends T), 即可以用T和T的子類來替換T,里氏替換原則。
2. 不變
不支援T的子類或者父類,只知支援T本身。
3.逆變
[-T], contravariant, 類似(? supers T) 只能用T的父類來替換T。是逆里氏替換原則。
上界:
只允許T的超類U來替換T。 [U >: T]
下界:
只允許T的子類U來替代T。 [U <: T]
理解概念後,以後讀原始碼的時候才不會不知雲。
-EOF-