原文:https://my.oschina.net/xinxingegeya/blog/486671
Scala的協變(+),逆變(-),上界(<:),下界(>:)
協變covariant、逆變contravariant、不可變invariant
對於一個帶類型參數的類型,比如 List[T],如果對A及其子類型B,滿足 List[B]也符合List[A]的子類型,那麼就稱爲covariance(協變) ,如果 List[A]是 List[B]的子類型,即與原來的父子關係正相反,則稱爲contravariance(逆變)。
如果一個類型支持協變或逆變,則稱這個類型爲variance(翻譯爲可變的或變型),否則稱爲invariance(不可變的)
在Java裏,泛型類型都是invariant,比如 List<String> 並不是 List<Object> 的子類型。
而scala支持,可以在定義類型時聲明(用加號表示爲協變,減號表示逆變),如:
trait List[+T] // 在類型定義時聲明爲協變
這樣會把List[String]作爲List[Any]的子類型。
不過Java支持使用(use-site variance),也就是在聲明變量時
List<? extends Object> list = new ArrayList<String>();
如下面的代碼示例
List<String> aList = new ArrayList<String>();
List<? extends Object> covariantList = aList;
List<? super String> contravariantList = aList;
covariantList.add("d"); //wrong
Object a = covariantList.get(0);
contravariantList.add("d"); //OK
String b = contravariantList.get(1); //wrong
Object c = contravariantList.get(2);
Java的上界和下界
使用extends關鍵字確定參數化類型的上界
在這裏參數化類型是Date,也有可能是Date的子類,所以add方法受限制
@Test
public void upperBound(List<? extends Date> list, Date date) {
Date now = list.get(0);
System.out.println("now==>" + now);
//list.add(date); //這句話無法編譯,實際調用時傳入的list可能是java.util.Date的某個子類的參數化類型
list.add(null);//這句可以編譯,因爲null沒有類型信息
}
使用super關鍵字確定參數化類型的下界
在這裏,參數化類型有可能是Timestamp,有可能是其父類(包括Object)
public void lowerBound(List<? super Timestamp> list) {
Timestamp now = new Timestamp(System.currentTimeMillis());
list.add(now);
//Timestamp time = list.get(0); //不能編譯。方法返回的對象類型可能是Date甚至是Object,不能安全的向下轉換到Timestamp,也就因此無法編譯了
}
Scala的協變和逆變
object app_main_1 extends App {
val t: Temp[Super] = new Temp[Sub]("hello world")
print(t.toString)
}
class Temp[+A](title: String)
//支持協變
class Super
class Sub extends Super
但要注意的是,variance(變型)並不會被繼承,父類聲明爲variance(可變類型),子類如果想要保持,仍需要聲明成可變類型
如下所示,
scala> trait A[+T]
defined trait A
scala> class C[T] extends A[T]
defined class C
scala> class X; class Y extends X;
defined class X
defined class Y
scala> val c:C[X] = new C[Y]
<console>:11: error: type mismatch;
found : C[Y]
required: C[X]
Note: Y <: X, but class C is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
val c:C[X] = new C[Y]
^
scala>
You may wish to define T as +T instead. (SLS 4.5)
應該寫成下面這樣,
scala> class C[+T] extends A[T]
defined class C
scala> val c:C[X] = new C[Y]
c: C[X] = C@7241a47d
scala>
Scala逆變和協變的實例分析
在Scala(以及其他許多編程語言)中,函數也是對象,可以使用、定義其他對象的地方,也可以使用、定義函數。Scala中的函數,具有apply方法的類的實例,就可以當做函數來使用。其中apply接受的參數就是函數的參數,而apply的返回值就是函數的返回值。
首先給出一個接受一個參數的函數的泛型定義。
trait Function1[-T, +U] {
def apply(x: T): U
}
這種函數接受一個參數,參數類型爲泛型類型T,返回類型爲泛型類型U。和其他支持泛型的語言一樣,實際定義函數時T和U的類型會被確定下來,不過需要注意的是,這邊的T之前有一個“-”,而U之前有一個“+”。
在這裏引入關於這個符號的說明,在聲明Scala的泛型類型時,“+”表示協變,而“-”表示逆變
C[+T]:如果A是B的子類,那麼C[A]是C[B]的子類。>>>>>協變
C[-T]:如果A是B的子類,那麼C[B]是C[A]的子類。>>>>>逆變
C[T]:無論A和B是什麼關係,C[A]和C[B]沒有從屬關係。
根據Liskov替換原則,如果A是B的子類,那麼能適用於B的所有操作,都適用於A。讓我們看看這邊Function1的定義,是否滿足這樣的條件。假設Bird是Animal的子類,那麼看看下面兩個函數之間是什麼關係:
def f1(x: Bird): Animal // instance of Function1[Bird, Animal]
def f2(x: Animal): Bird // instance of Function1[Animal, Bird]
在這裏f2的類型是f1的類型的子類。爲什麼?
我們先看一下參數類型,根據Liskov替換原則,f1能夠接受的參數,f2也能接受 。在這裏f1接受的Bird類型,f2顯然可以接受,因爲Bird對象可以被當做其父類Animal的對象來使用。
再看返回類型,f1的返回值可以被當做Animal的實例使用,f2的返回值可以被當做Bird的實例使用,當然也可以被當做Animal的實例使用。
所以我們說,函數的參數類型是逆變的,而函數的返回類型是協變的。
函數的參數類型是逆變的,而函數的返回類型是協變的
那麼我們在定義Scala類的時候,是不是可以隨便指定泛型類型爲協變或者逆變呢?答案是否定的。通過上面的例子可以看出,如果將Function1的參數類型定義爲協變,或者返回類型定義爲逆變,都會違反Liskov替換原則,因此,Scala規定,協變類型只能作爲方法的返回類型,而逆變類型只能作爲方法的參數類型。類比函數的行爲,結合Liskov替換原則,就能發現這樣的規定是非常合理的。
這種函數的泛型特性對於函數式編程非常有用。儘管C++的泛型在語法層面上不支持協變與逆變,但在C++11的function<U(T)>中,返回類型U和參數類型T也同樣遵循與Scala相同的協變與逆變規則。
注: 里氏替換原則中說,任何基類可以出現的地方,子類一定可以出現。
參考:http://blog.csdn.net/oopsoom/article/details/24773239
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。是逆里氏替換原則。
Scala上界(<:)和下界(>:)
1) U >: T
這是類型下界的定義,也就是U必須是類型T的父類(或本身,自己也可以認爲是自己的父類)。
2) S <: T
這是類型上界的定義,也就是S必須是類型T的子類(或本身,自己也可以認爲是自己的子類)。
如何使用上界和下界,看一個綜合示例,
package com.usoft2
//出版物類
class Publication(val title: String)
//書籍類
class Book(title: String) extends Publication(title)
//圖書庫類
object Library {
//定義圖書庫內所有的書籍
val books: Set[Book] = Set(
new Book("Programming in Scala"),
new Book("Walden")
)
//打印所有圖書內容,使用外部傳入的函數來實現
def printBookList(info: Book => AnyRef) {
//確認Scala中一個參數的函數實際上是Function1特徵的實例
assert(info.isInstanceOf[Function1[_, _]])
//打印
for (book <- books)
println(info(book))
}
//打印所有圖書內容,使用外部傳入的GetInfoAction特徵的實例來實現
def printBokkListByTrait[P >: Book, R <: AnyRef](action: GetInfoAction[P, R]) {
//打印
for (book <- books)
println(action(book))
}
}
//取得圖書內容特徵,P類型參數的類型下界是Book,R類型參數的類型上界是AnyRef
trait GetInfoAction[P >: Book, R <: AnyRef] {
//取得圖書內容的文本描述,對應()操作符
def apply(book: P): R
}
//單例對象,文件的主程序
object Customer extends App {
//定義取得出版物標題的函數
def getTitle(p: Publication): String = p.title
//使用函數來打印
Library.printBookList(getTitle)
//使用特徵GetInfoAction的實例來打印
Library.printBokkListByTrait(new GetInfoAction[Publication, String] {
def apply(p: Publication): String = p.title
})
}
參考:
http://hongjiang.info/scala-covariance-and-contravariance/
http://fineqtbull.iteye.com/blog/477994
http://deltamaster.is-programmer.com/posts/48772.html?utm_source=tuicool
================================END================================