假設A <: B, 可以簡單認為A是B的子類(A <: B的具體定義是:任何B滿足的性質,A都滿足):
A<:B的前提下,有如下對協變逆變的定義:
C[A] <: C[B] C 是 協變的 covariantC[A] >: C[B] C 是 逆變的 contravariantC[A]和C[B]都不是彼此的子類 C是 非變的 nonvariantC是AnyRef類,即引用類,例如Int是值類,List則是引用類,這是因為List需要定義其元素的類型,比如List[Int]
C[A]代表C的元素是A類對象,C[A]<:C[B]代表C[B]是C[A]基類,因此C[B]可以賦值給C[A]
Scala可以用下面的方式更簡單聲明容器類的性質
Class C[+A] {..} C 是 協變的 covariantClass C[-A] {…} C 是 逆變的 contravariantClass C[A]{…} C是 非變的 nonvariant例子
Trait List[+T]{ Def perpend(elem:T):List[T] = new Cons(elem, this)}假設基類Inset有兩個子類nonEmpty和empty這個定義是錯誤的,當objectA 是List[nonempty], 語句:objectA.PRepend(empty) 會導致類型匹配錯誤。
prepend是屬于trait List[+T]的方法
(這里List[+T]的用處是,當有對象A為List[NonEmpty], 注意是List對象不是NonEmpty對象,則可以把對象A賦值給一個需要List[Inset]的參數,因為List類是協變的------nonEmpty是Inset子類,List[NonEmpty]便是List[Inset]的子類)
出現錯誤的情形:
假設此時this是一個List[nonempty]的對象,
則List[T](List[+T]賦予List的性質在這里不用考慮了),編譯器會識別T是nonEmpty類,所以如果this.prepend(empty)會導致類型錯誤,因為empty和nonEmpty都是InSet的子類,但是empty不是nonEmpty的子類,不能賦值給需要nonEmpty的參數位置(elem:T)。
正確的定義方式:
Def perpend[U>:T](elem: U) :List[U] =new Cons(elem, this)
U>:T 表示:U是T的基類,T是U的子類。因此原先List[nonEmpty]的對象,T是nonEmpty,但是U是T的基類,所以U是Inset。傳入prepend方法的元素要求是Inset類型,empty是Inset子類當然可以傳入,得到的new Cons(empty, this)是List[Inset],毫無問題
應用
1 ).選擇支持協變的容器類:
1. Inset class 中有NoEmpty 和 empty兩個子類。
2. Array不是協變,即定義abstractclass Array[T] extends Seq[T]。
3. 如果Array換成List, 因為定義abstractclass List[+T] extends Seq[T],List是協變,則NonEmpty是Inset的子類,List[NonEmpty]是List[Inset]的子類,第二行的a可以賦值給b。類似于c++的多態,子類指針和子類引用是可以分別賦值給基類指針和基類引用
2). 定義新的函數對象,例如
object addOne extends Function1[Int,Int]{ def apply(m:Int):Int= m + 1}利用函數的協變:If A2 <: A1 并且 B1<:B2 則有:
A1=> B1 <: A2 => B2
因此scala中的Function1特性的定義如下:
Package scala
Trait Function1[-T, +U] { def apply(x:T) : U
}
假設有兩個Function1:
scala> val f1: Int => String = x=> s"Int($x)"
f1: Int => String = <function1>
scala> val f2: Any => String = x=> s"Any($x)"
f2: Any => String = <function1>
凡是f1可以使用的地方,f2都是可以用的(考慮基類子類的關系:基類可以使用的場景,子類都可以使用);但反過來不行。
所以類型Function1[Any, String]應該是Function1[Int, String]的子類, 是Trait Function1[-T, +U]
的定義賦予了Function1類更豐富的繼承關系。
逆變的傳入類型,允許子類在重寫基類的函數時,傳入參數的類型比基類原函數定義的傳入參數類型更廣泛。
而協變的返回類型,允許了子類在重寫基類的函數時,可以返回比基類原函數定義的返回類型更加具體的類型。
新聞熱點
疑難解答