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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

Scala入门到精通——第二十一节 类型参数(三)-协变与逆变

發布時間:2024/1/23 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Scala入门到精通——第二十一节 类型参数(三)-协变与逆变 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本節主要內容

  • 協變
  • 逆變
  • 類型通匹符
  • 1. 協變

    協變定義形式如:trait List[+T] {} 。當類型S是類型A的子類型時,則List[S]也可以認為是List[A}的子類型,即List[S]可以泛化為List[A]。也就是被參數化類型的泛化方向與參數類型的方向是一致的,所以稱為協變(covariance)。


    圖1 協變示意圖

    為方便大家理解,我們先分析Java語言中為什么不存在協變及下一節要講的逆變。下面的java代碼證明了Java中不存在協變:

    java.util.List<String> s1=new LinkedList<String>();java.util.List<Object> s2=new LinkedList<Object>(); //下面這條語句會報錯//Type mismatch: cannot convert from// List<String> to List<Object>s2=s1;

    雖然在類層次結構上看,String是Object類的子類,但List<String>并不是的List<Object>子類,也就是說它不是協變的。java的靈活性就這么差嗎?其實java不提供協變和逆變這種特性是有其道理的,這是因為協變和逆變會破壞類型安全。假設java中上面的代碼是合法的,我們此時完全可以s2.add(new Person(“搖擺少年夢”)往集合中添加Person對象,但此時我們知道, s2已經指向了s1,而s1里面的元素類型是String類型,這時其類型安全就被破壞了,從這個角度來看,java不提供協變和逆變是有其合理性的。

    Scala語言相比java語言提供了更多的靈活性,當不指定協變與逆變時,它和java是一樣的,例如:

    //定義自己的List類 class List[T](val head: T, val tail: List[T]) object NonVariance {def main(args: Array[String]): Unit = {//編譯報錯//type mismatch; found : //cn.scala.xtwy.covariance.List[String] required://cn.scala.xtwy.covariance.List[Any] //Note: String <: Any, but class List //is invariant in type T. //You may wish to define T as +T instead. (SLS 4.5)val list:List[Any]= new List[String]("搖擺少年夢",null) } }
    • 13

    可以看到,當不指定類為協變的時候,而是一個普通的scala類,此時它跟java一樣是具有類型安全的,稱這種類是非變的(Nonvariance)。scala的靈活性在于它提供了協變與逆變語言特點供你選擇。上述的代碼要使其合法,可以定義List類是協變的,泛型參數前面用+符號表示,此時List就是協變的,即如果T是S的子類型,那List[T]也是List[S]的子類型。代碼如下:

    //用+標識泛型T,表示List類具有協變性 class List[+T](val head: T, val tail: List[T]) object NonVariance {def main(args: Array[String]): Unit = {val list:List[Any]= new List[String]("搖擺少年夢",null) } }

    上述代碼將List[+T]滿足協變要求,但往List類中添加方法時會遇到問題,代碼如下:

    class List[+T](val head: T, val tail: List[T]) {//下面的方法編譯會出錯//covariant type T occurs in contravariant position in type T of value newHead//編譯器提示協變類型T出現在逆變的位置//即泛型T定義為協變之后,泛型便不能直接//應用于成員方法當中def prepend(newHead:T):List[T]=new List(newHead,this) } object Covariance {def main(args: Array[String]): Unit = {val list:List[Any]= new List[String]("搖擺少年夢",null) } }
    • 5

    那如果定義其成員方法呢?必須將成員方法也定義為泛型,代碼如下:

    class List[+T](val head: T, val tail: List[T]) {//將函數也用泛型表示//因為是協變的,輸入的類型必須是T的超類def prepend[U>:T](newHead:U):List[U]=new List(newHead,this)override def toString()=""+head } object Covariance {def main(args: Array[String]): Unit = {val list:List[Any]= new List[String]("搖擺少年夢",null) println(list)} }
    • 2

    2. 逆變

    逆變定義形式如:trait List[-T] {}
    當類型S是類型A的子類型,則Queue[A]反過來可以認為是Queue[S}的子類型。也就是被參數化類型的泛化方向與參數類型的方向是相反的,所以稱為逆變(contravariance)。 下面的代碼給出了逆變與協變在定義成員函數時的區別:

    圖2 逆變示意圖

    //聲明逆變 class Person2[-A]{ def test(x:A){} }//聲明協變,但會報錯 //covariant type A occurs in contravariant position in type A of value x class Person3[+A]{ def test(x:A){} }
    • 1

    要理解清楚后面的原理,先要理解清楚什么是協變點(covariant position) 和 逆變點(contravariant position)。

    圖2 協變點

    圖3 逆變點
    我們先假設class Person3[+A]{ def test(x:A){} } 能夠編譯通過,則對于Person3[Any] 和 Person3[String] 這兩個父子類型來說,它們的test方法分別具有下列形式:

    //Person3[Any] def test(x:Any){}//Person3[String] def test(x:String){}
    • 1

    由于AnyRef是String類型的父類,由于Person3中的類型參數A是協變的,也即Person3[Any]是Person3[String]的父類,因此如果定義了val pAny=new Person3[AnyRef]、val pString=new Person3[String],調用pAny.test(123)是合法的,但如果將pAny=pString進行重新賦值(這是合法的,因為父類可以指向子類,也稱里氏替換原則),此時再調用pAny.test(123)時候,這是非法的,因為子類型不接受非String類型的參數。也就是父類能做的事情,子類不一定能做,子類只是部分滿足。
    為滿足里氏替換原則,子類中函數參數的必須是父類中函數參數的超類,這樣的話父類能做的子類也能做。因此需要將類中的泛型參數聲明為逆變或不變的。class Person2[-A]{ def test(x:A){} },我們可以對Person2進行分析,同樣聲明兩個變量:val pAnyRef=new Person2[AnyRef]、val pString=new Person2[String],由于是逆變的,所以Person2[String]是Person2[AnyRef]的超類,pAnyRef可以賦值給pString,從而pString可以調用范圍更廣泛的函數參數(比如未賦值之前,pString.test(“123”)函數參數只能為String類型,則pAnyRef賦值給pString之后,它可以調用test(x:AnyRef)函數,使函數接受更廣泛的參數類型。方法參數的位置稱為做逆變點(contravariant position),這是class Person3[+A]{ def test(x:A){} }會報錯的原因。為使class Person3[+A]{ def test(x:A){} }合法,可以利用下界進行泛型限定,如:

    class Person3[+A]{ def test[R>:A](x:R){} }

    將參數范圍擴大,從而能夠接受更廣泛的參數類型。

    通過前述的描述,我們弄明白了什么是逆變點,現在我們來看一下什么是協變點,先看下面的代碼:

    //下面這行代碼能夠正確運行 class Person4[+A]{ def test:A=null.asInstanceOf[A] } //下面這行代碼會編譯出錯 //contravariant type A occurs //in covariant position in type ? A of method test class Person5[-A]{ def test:A=null.asInstanceOf[A] }

    這里我們同樣可以通過里氏替換原則來進行說明

    scala> class Person[+A]{def f():A=null.asInstanceOf[A]} defined class Personscala> val p1=new Person[AnyRef]() p1: Person[AnyRef] = Person@8dbd21scala> val p2=new Person[String]() p2: Person[String] = Person@1bb8caescala> p1.f res0: AnyRef = nullscala> p2.f res1: String = null

    可以看到,定義為協變時父類的處理范圍更廣泛,而子類的處理范圍相對較小;如果定義協變的話,正好與此相反。

    3. 類型通配符

    類型通配符是指在使用時不具體指定它屬于某個類,而是只知道其大致的類型范圍,通過”_ <:” 達到類型通配的目的,如下面的代碼

    class Person(val name:String){override def toString()=name }class Student(name:String) extends Person(name) class Teacher(name:String) extends Person(name)class Pair[T](val first:T,val second:T){override def toString()="first:"+first+" second: "+second; }object TypeWildcard extends App {//Pair的類型參數限定為[_<:Person],即輸入的類為Person及其子類//類型通配符和一般的泛型定義不一樣,泛型在類定義時使用,而類型能配符號在使用類時使用def makeFriends(p:Pair[_<:Person])={println(p.first +" is making friend with "+ p.second)}makeFriends(new Pair(new Student("john"),new Teacher("搖擺少年夢"))) } 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的Scala入门到精通——第二十一节 类型参数(三)-协变与逆变的全部內容,希望文章能夠幫你解決所遇到的問題。

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