Scala入门到精通——第十一节 Trait进阶
本節主要內容
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被構造完之后,才會構造本類
- 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 提前定義
提前定義是指在常規構造之前將變量初始化,完整代碼如下:
- 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进阶的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Scala入门到精通——第十节 Scal
- 下一篇: Scala入门到精通——第十三节 高阶函