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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

Scala入门到精通——第十一节 Trait进阶

發布時間:2024/1/23 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Scala入门到精通——第十一节 Trait进阶 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

本節主要內容

  • trait構造順序
  • trait與類的比較
  • 提前定義與懶加載
  • trait擴展類
  • self type
  • 1 trait構造順序

    在前一講當中我們提到,對于不存在具體實現及字段的trait,它最終生成的字節碼文件反編譯后是等同于Java中的接口,而對于存在具體實現及字段的trait,其字節碼文件反編譯后得到的java中的抽象類,它有著Scala語言自己的實現方式。因此,對于trait它也有自己的構造器,trait的構造器由字段的初始化和其它trait體中的語句構成,下面是其代碼演示:

    package cn.scala.xtwyimport java.io.PrintWritertrait Logger{println("Logger")def log(msg:String):Unit }trait FileLogger extends Logger{println("FilgeLogger")val fileOutput=new PrintWriter("file.log")fileOutput.println("#")def log(msg:String):Unit={fileOutput.print(msg)fileOutput.flush()} } object TraitDemo{def main(args: Array[String]): Unit = {//匿名類new FileLogger{ }.log("trat demo")} } //打印輸出內容為: Logger FilgeLogger //創建文件file.log,內容為 # trat demo

    通過上述不難發現,在創建匿名類對象時,先調用的是Logger類的構造器,然后調用的是FileLogger的構造器。實際上構造器是按以下順序執行的:
    1. 如果有超類,則先調用超類的構造器
    2. 如果有父trait,它會按照繼承層次先調用父trait的構造器
    2. 如果有多個父trait,則按順序從左到右執行
    3. 所有父類構造器和父trait被構造完之后,才會構造本類

    class Person class Student extends Person with FileLogger with Cloneable 上述構造器的執行順序為: 1 首先調用父類Person的構造器 2 調用父trait Logger的構造器 3 再調用trait FileLogger構造器,再然后調用Cloneable的構造器 4 最后才調用Student的構造器
    • 1

    2 trait與類的比較

    通過前一小節,可以看到,trait有自己的構造器,它是無參構造器,不能定義trait帶參數的構造器,即:

    //不能定義trait帶參數的構造器 trait FileLogger(msg:String)
    • 1

    除此之外 ,trait與普通的scala類并沒有其它區別,在前一講中我們提到,trait中可以有具體的、抽象的字段,也可以有具體的、抽象的方法,即使trait中沒有抽象的方法也是合理的,如:

    //FileLogger里面沒有抽象的方法 trait FileLogger extends Logger{println("FilgeLogger")val fileOutput=new PrintWriter("file.log")fileOutput.println("#")def log(msg:String):Unit={fileOutput.print(msg)fileOutput.flush()} }

    3. 提前定義與懶加載

    前面的FileLogger中的文件名被寫死為”file.log”,程序不具有通用性,這邊對前面的FileLogger進行改造,把文件名寫成參數形式,代碼如下:

    import java.io.PrintWritertrait Logger{def log(msg:String):Unit }trait FileLogger extends Logger{//增加了抽象成員變量val fileName:String//將抽象成員變量作為PrintWriter參數val fileOutput=new PrintWriter(fileName:String)fileOutput.println("#")def log(msg:String):Unit={fileOutput.print(msg)fileOutput.flush()} }
    • 1

    這樣的設計會存在一個問題,雖然子類可以對fileName抽象成員變量進行重寫,編譯也能通過,但實際執行時會出空指針異常,完全代碼如下:

    package cn.scala.xtwyimport java.io.PrintWritertrait Logger{def log(msg:String):Unit }trait FileLogger extends Logger{//增加了抽象成員變量val fileName:String//將抽象成員變量作為PrintWriter參數val fileOutput=new PrintWriter(fileName:String)fileOutput.println("#")def log(msg:String):Unit={fileOutput.print(msg)fileOutput.flush()} }class Person class Student extends Person with FileLogger{//Student類對FileLogger中的抽象字段進行重寫val fileName="file.log" }object TraitDemo{def main(args: Array[String]): Unit = {new Student().log("trait demo")} }
    • 1

    上述代碼在編譯時不會有問題,但實際執行時會拋異常,異常如下:

    Exception in thread "main" java.lang.NullPointerExceptionat java.io.FileOutputStream.<init>(Unknown Source)at java.io.FileOutputStream.<init>(Unknown Source)at java.io.PrintWriter.<init>(Unknown Source)at cn.scala.xtwy.FileLogger$class.$init$(TraitDemo.scala:12)at cn.scala.xtwy.Student.<init>(TraitDemo.scala:22)at cn.scala.xtwy.TraitDemo$.main(TraitDemo.scala:28)at cn.scala.xtwy.TraitDemo.main(TraitDemo.scala)

    具體原因就是構造器的執行順序問題,

    class Student extends Person with FileLogger{//Student類對FileLogger中的抽象字段進行重寫val fileName="file.log" } //在對Student類進行new操作的時候,它首先會 //調用Person構造器,這沒有問題,然后再調用 //Logger構造器,這也沒問題,但它最后調用FileLogger //構造器的時候,它會執行下面兩條語句 //增加了抽象成員變量val fileName:String//將抽象成員變量作為PrintWriter參數val fileOutput=new PrintWriter(fileName:String) 此時fileName沒有被賦值,被初始化為null,在執行new PrintWriter(fileName:String)操作的時候便拋出空指針異常

    有幾種辦法可以解決前面的問題:
    1 提前定義
    提前定義是指在常規構造之前將變量初始化,完整代碼如下:

    package cn.scala.xtwyimport java.io.PrintWritertrait Logger{def log(msg:String):Unit }trait FileLogger extends Logger{val fileName:Stringval fileOutput=new PrintWriter(fileName:String)fileOutput.println("#")def log(msg:String):Unit={fileOutput.print(msg)fileOutput.flush()} }class Person class Student extends Person with FileLogger{val fileName="file.log" }object TraitDemo{def main(args: Array[String]): Unit = {val s=new {//提前定義override val fileName="file.log"} with Students.log("predifined variable ")} }
    • 1

    顯然,這種方式編寫的代碼很不優雅,也比較難理解。此時可以通過在第一講中提到的lazy即懶加載的方式

    2 lazy懶加載的方式

    package cn.scala.xtwyimport java.io.PrintWritertrait Logger{def log(msg:String):Unit }trait FileLogger extends Logger{val fileName:String//將方法定義為lazy方式lazy val fileOutput=new PrintWriter(fileName:String)//下面這條語句不能出現,否則同樣會報錯//因此,它是FileLogger構造器里面的方法//在構造FileLogger的時候便會執行//fileOutput.println("#")def log(msg:String):Unit={fileOutput.print(msg)fileOutput.flush()} }class Person class Student extends Person with FileLogger{val fileName="file.log" }object TraitDemo{def main(args: Array[String]): Unit = {val s=new Students.log("predifined variable ")} }
    • 1

    lazy方式定義fileOutput只有當真正被使用時才被初始化,例子中,當調用 s.log(“predifined variable “)時,fileOutput才被初始化,此時fileName已經被賦值了。

    4 trait擴展類

    在本節的第2小節部分,我們給出了trait與類之間的區別,我們現在明白,trait除了不具有帶參數的構造函數之外,與普通類沒有任何區別,這意味著trait也可以擴展其它類

    trait Logger{def log(msg:String):Unit } //trait擴展類Exception trait ExceptionLogger extends Exception with Logger{def log(msg:String):Unit={println(getMessage())} }
    • 1

    如果此時定義了一個類混入了ExceptionLogger ,則Exception自動地成為這個類的超類,代碼如下:

    trait Logger{def log(msg:String):Unit }trait ExceptionLogger extends Exception with Logger{def log(msg:String):Unit={println(getMessage())} }//類UnprintedException擴展自ExceptionLogger //注意用的是extends //此時ExceptionLogger父類Exception自動成為 //UnprintedException的父類 class UnprintedException extends ExceptionLogger{override def log(msg:String):Unit={println("")} }

    當UnprintedException擴展的類或混入的特質具有相同的父類時,scala會自動地消除沖突,例如:

    //IOException具有父類Exception //ExceptionLogger也具有父類Exception //scala會使UnprintedException只有一個父類Exception class UnprintedException extends IOException with ExceptionLogger{override def log(msg:String):Unit={println("")} }

    5 self type

    下面的代碼演示了什么是self type即自身類型

    class A{//下面 self => 定義了this的別名,它是self type的一種特殊形式//這里的self并不是關鍵字,可以是任何名稱self => val x=2 //可以用self.x作為this.x使用def foo = self.x + this.x }

    下面給出了內部類中使用場景

    class OuterClass { outer => //定義了一個外部類別名val v1 = "here"class InnerClass {// 用outer表示外部類,相當于OuterClass.thisprintln(outer.v1) } }

    而下面的代碼則定義了自身類型self type,它不是前面別名的用途,

    trait X{} class B{//self:X => 要求B在實例化時或定義B的子類時//必須混入指定的X類型,這個X類型也可以指定為當前類型self:X=> }

    自身類型的存在相當于讓當前類變得“抽象”了,它假設當前對象(this)也符合指定的類型,因為自身類型 this:X =>的存在,當前類構造實例時需要同時滿足X類型,下面給出自身類型的使用代碼:

    trait X{def foo() } class B{self:X=> } //類C擴展B的時候必須混入trait X //否則的話會報錯 class C extends B with X{def foo()=println("self type demo") }object SelfTypeDemo extends App{println(new C().foo) }

    總結

    以上是生活随笔為你收集整理的Scala入门到精通——第十一节 Trait进阶的全部內容,希望文章能夠幫你解決所遇到的問題。

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