日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Scala教程之:深入理解协变和逆变

發布時間:2024/2/28 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Scala教程之:深入理解协变和逆变 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 函數的參數和返回值
    • 可變類型的變異

在之前的文章中我們簡單的介紹過scala中的協變和逆變,我們使用+ 來表示協變類型;使用-表示逆變類型;非轉化類型不需要添加標記。

假如我們定義一個class C[+A] {} ,這里A的類型參數是協變的,這就意味著在方法需要參數是C[AnyRef]的時候,我們可以是用C[String]來代替。

同樣的道理如果我們定義一個class C[-A] {}, 這里A的類型是逆變的,這就意味著在方法需要參數是C[String]的時候,我們可以用C[AnyRef]來代替。

注意:變異標記只有在類型聲明中的類型參數里才有意義,對參數化的方法沒有意義,因為該標記影響的是子類繼承行為,而方法沒有子類。例如List.map 方法的簡化簽名:

sealed abstract class List[+A] ... { // 忽略了混入的trait ... def map[B](f: A => B): List[B] = {...} ... }

這里方法map的類型參數B是不能使用變異標記的,如果你修改其變異標記,則會返回編譯錯誤。

函數的參數和返回值

現在我們討論scala中函數參數的一個非常重要的結論:函數的參數必須是逆變的,而返回值必須是協變的

為什么呢?

接下來我們考慮scala內置的帶一個參數的函數類型Function1,其簡化的定義如下:

trait Function1[-T1, +R] extends AnyRef { self =>/** Apply the body of this function to the argument.* @return the result of function application.*/def apply(v1: T1): R...override def toString() = "<function1>" }

我們知道類似 A=>B 的形式在scala中是可以自動被轉換為Function1的形式。

scala> var f: Int=>Int = i=>i+1 f: Int => Int = <function1>

實際上其會被轉換成為如下的形式:

val f: Int => Int = new Function1[Int,Int] { def apply(i: Int): Int = i + 1 }

假如我們定義了三個class 如下:

class CSuper { def msuper() = println("CSuper") } class C extends CSuper { def m() = println("C") } class CSub extends C { def msub() = println("CSub") }

我們可以定義如下幾個f:

var f: C => C = (c: C) => new C // ? f = (c: CSuper) => new CSub // ? f = (c: CSuper) => new C // ? f = (c: C) => new CSub // ? f = (c: CSub) => new CSuper // ? 編譯錯誤!

根據Function1[-T1, +R]的定義,2-5可以通過編譯,而6會編譯失敗。

怎么理解6呢? 這里我們要區分兩個概念,函數的定義類型和函數的運行類型。

這里f的定義類型是 C=>C。 當f = (c: CSub) => new CSuper時,它的實際apply方法就是:

def apply(i: CSub): CSuper = new CSuper

CSub=>CSuper就是f的運行類型。

在apply中可以能調用到CSub特有的方法,例如:msub(),而返回的CSuper又缺少了C中的方法 m()。

如果用戶在調用該f的時候,還是按照定義的類型傳入C,并且期待返回的值是C時候,就會發生錯誤。 因為實際的類型是按照傳入CSub和返回CSuper來定義的。

如果實際的函數類型為(x:CSuper)=> Csub,該函數不僅可以接受任何C 類值作為參數,也可以處理C 的父類型的實例,或其父類型的其他子類型的實例(如果存在的話)。所以,由于只傳入C 的實例,我們永遠不會傳入超出f 允許范圍外的參數。從某種意義上說,f 比我們需要的更加“寬容”。

同樣,當它只返回Csub 時,這也是安全的。因為調用方可以處理C 的實例,所以也一定可以處理CSub 的實例。在這個意義上說,f 比我們需要的更加“嚴格”。

如果函數的參數使用了協變,返回值使用了逆變則會編譯失敗:

scala> trait MyFunction2[+T1, +T2, -R] { | def apply(v1:T1, v2:T2): R = ??? | } <console>:37: error: contravariant type R occurs in covariant position in type (v1: T1, v2: T2)R of method apply def apply(v1:T1, v2:T2): R = ??? ^ <console>:37: error: covariant type T1 occurs in contravariant position in type T1 of value v1 def apply(v1:T1, v2:T2): R = ??? ^ <console>:37: error: covariant type T2 occurs in contravariant position in type T2 of value v2 def apply(v1:T1, v2:T2): R = ??? ^

可變類型的變異

上面我們講的情況下,class的參數化類型是不可變的,如果class的參數類型是可變的話,會是什么樣的情況呢?

scala> class ContainerPlus[+A](var value: A) <console>:34: error: covariant type A occurs in contravariant position in type A of value value_= class ContainerPlus[+A](var value: A) ^ scala> class ContainerMinus[-A](var value: A) <console>:34: error: contravariant type A occurs in covariant position in type => A of method value class ContainerMinus[-A](var value: A)

通過上面的例子,我們也可以得到一個結論,可變參數化類型是不能變異的

假如可變參數是協變的ContainerPlus[+A],那么對于:

val cp: ContainerPlus[C]=new ContainerPlus(new CSub)

定義的類型是C,但是運行時類型是CSub,如果需要對類型變量重新賦值時就會遇到將C賦值給CSub的情況,會出現編譯錯誤。

如果可變參數是逆變的ContainerPlus[-A],那么對于:

val cm: ContainerMinus[C] = new ContainerMinus(new CSuper)

定義的類型是C,但是運行時類型是CSuper,那么對于期望的返回類型是C,但是實際返回類型是CSuper,也會發生錯誤。

所以可變參數化類型是不能變異的。

更多精彩內容且看:

  • 區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新
  • Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
  • Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
  • java程序員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程

更多教程請參考 flydean的博客

總結

以上是生活随笔為你收集整理的Scala教程之:深入理解协变和逆变的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。