Scala提供「特質」而非接口。特質能夠同時擁有抽象方法和具體方法,而類能夠實現多個特質。java
Scala特質徹底能夠像Java的接口那樣工做。例如:app
trait Logger{ def log(msg: String) //這個是抽象方法 }
注意,你不須要講方法聲明爲abstract,特質中未被實現的方法默認就是抽象的。ide
子類能夠給出實現:工具
class ConsoleLogger extends Logger{ //用extends,而不是implements def log(msg: String){ println(msg) } //不須要寫override }
若是你須要的特質不止一個,能夠用with關鍵字來添加額外的特質:this
class ConsoleLogger extends Logger with Cloneable with Serializable
全部的Java接口均可以做爲Scala特質使用,和Java同樣,Scala類只能有一個超類,但能夠有任意數量的特質。spa
在Scala中,特質中的方法並不須要必定是抽象的。舉例來講,咱們能夠把咱們的具體類變成一個特質:scala
trait Logger{ def log(msg: String){ println(msg) } }
如何使用:日誌
class ConsoleLogger extends Logger{ def write(msg: String){ log("Hello World")} }
首先,特質也是能夠被奇特特質擴展的code
trait Logger{ def log(msg: String){ log("Hello World") } } trait ConsoleLogger extends Logger{ override def log(msg: String){ println(「ConsoleLogger:」 + msg) } } trait FileLogger extends Logger{ override def log(msg: String){ println(「FileLogger:」 + msg) } }
你能夠在構造對象的時候介入這個特質:server
val acct = new SavingsAccount with ConsoleLogger
當咱們在acct對象上調用log方法時,ConsoleLogger特質的log方法就會被執行。固然了,另外一個對象能夠加入不一樣的特質:
val act = new new SavingsAccount with FileLogger
//日誌特質 trait Logger{ def log(msg: String){} } //打印日誌 trait ConsoleLogger{ def log(msg: String){ println(msg) } } //給日誌添加時間戳 trait TimestampLogger extends Logged{ override def log(msg: String){ super.log(new java.util.Date() + " " + msg) } } //過長日誌截斷 trait ShortLogger extends Logged{ val maxLength = 15 override def log(msg: String){ super.log(if(msg.length <= maxLength) msg else msg.substring(0, maxLength -3) + "...") } } //存款類 class SavingsAccount extends Logged{ def withdraw(amount: Double){ if(amount: Double) log("Insufficient funds") else ... } }
特質的調用,要根據特指添加的順序界定。通常來講特質是從最後一個開始被處理,例如:
val acct1 = new SavingsAccount with ConsoleLogger with TimestampLogger with ShortLogger val acct2 = new SavingsAccount with ConsoleLogger with ShortLogger with TimestampLogger
若是咱們調用acct1中的withdraw方法,咱們將獲得這樣的消息:
Sun Feb 06 17:45:45 ICT 2011 Insufficient ...
由於ShortLogger首先執行,而後他的super.log調用的是TimestampLogger。
可是,執行acct2的withdraw方法輸出的是:
Sun Feb 06 1...
這裏,TimestampLogger在特質列表中最後出現。器log方法首先被調用,以後調用ShortLogger被截斷
//日誌特質 trait Logger{ def log(msg: String) //這是個抽象方法 }
如今,讓咱們用時間戳特質來擴展它,就像前一節中示例那樣。很不幸,TimestampLogger 特質不能編譯了。
//給日誌添加時間戳 trait TimestampLogger extends Logged{ override def log(msg: String){ //重寫抽象方法 super.log(new java.util.Date() + " " + msg) //super.log定義了嗎? } }
根據正常的繼承規則,這個調用永遠都是錯的,Logger.log方法沒有實現。但實際上,吉祥前一節看到的咱們無法知道那個log方法最終被調用,這個取決於特質被混入的順序。
Scala認爲TimestampLogger 依舊是抽象的,所以必須給方法加上abstract關鍵字以及override關鍵字:
trait TimestampLogger extends Logged{ abstract override def log(msg: String){ super.log(new java.util.Date() + " " + msg) } }
特質包含了大量的工具方法,而這些工具方法能夠依賴一些抽象方法來實現。
trait Logger{ def log(msg: String) //這是個抽象方法 def info(msg: String) {log("INFO: " + msg)} def warn(msg: String) {log("WARN: " + msg)} def servere(msg: String) {log("SEVERE: " + msg)} }
注意咱們是怎樣把抽象方法和具體方法結合在一塊兒的。
這樣使用Logger特質的類就能夠任意調用這些日誌消息方法了。例如:
//存款類 class SavingsAccount extends Logged{ def withdraw(amount: Double){ if(amount: Double) servere("Insufficient funds") else ... } ... override def log(msg: String){ println(msg); } }
特質中的字段能夠試具體的能夠試抽象額,若是給了初始值,name字段就是具體的。
一般,對於特質中每個具體字段,使用該特製的類會得到一個字段預支對應。這些字段不是被繼承的;他們只是簡單的被加入到了子類當中。
在JVM中,一個類智能擴展一個超類,所以來自特質的字段不能以相同的方式繼承。因爲這個限制,特質中的字段被接入到了子類當中,並不是父類字段的繼承。
構造器以以下順序執行:
例如:
class SavingAccount extends Account with FileLogger with ShortLogger
構造器將按照以下的順序執行:
特質不能有構造器參數。每一個特質都有一個無參數的構造器。
說明:缺乏構造器參數是特質與類之間惟一的技術差異
提早定義:
trait Logger { def log(msg: String) } trait FileLogger extends Logger{ val filename: String val out = new PrintStream(filename) def log(msg: String){ out.println(msg); out.flush()} } class SavingsAccount extends Logged{ def withdraw(amount: Double){ if(amount: Double) servere("Insufficient funds") else ... } ... override def log(msg: String){ println(msg); } } val acct = new SavingsAccount with FileLogger { val filename = "Hello" //將會報錯,由於他的初始化順序在FileLogger 以後,使得FileLogger初始化時filename 字段未完成初始化報錯 }
解決辦法:
val acct = new { val filename = "Hello" } with SavingsAccount with FileLogger
若是你須要在類中作一樣的事,能夠這樣
class SavingsAccount extends{ val filename = "Hello" }with FileLogger { ... }
或者在FileLogger定義中使用懶值,可是並不高效
trait FileLogger extends Logger{ val filename: String lazy val out = new PrintStream(filename) def log(msg: String){ out.println(msg); out.flush()} }
一種不常見的用法是,特質也能夠擴展類。這個被特質擴展的類將會自動成爲全部混入該特質的超類。
假定有以下代碼:
trait LoggedException extends Exception with Logged{ def log() { log(getMessage()) } } class UnhappyException extends LoggedException{ ... }
那麼UnhappyException的超類自動變成LoggedException特質的擴展類Exception
若是咱們的類已經擴展了另外一個類怎麼辦?不要緊,只要是特質的超類的一個子類便可。若是咱們的類擴展了一個不相關的類,那麼就不可能混入這個特質了。
當特質擴展類時,編譯器可以確保的一件事是全部混入該特質的類都認爲這個類做超類。Scala還有另外一套機制能夠保證這一點:自身類型。
當特質以以下代碼開始定義時:
this:類型 =>
它便只能被混入指定類型的子類
trait FileLogger extends Logger{ this: Test => //Test必須是接口或者特質 def log(msg: String){ println(msg); } }
自身類型一樣也能夠處理結構類型,這種類型只給出類必須擁有的方法,而不是特質或接口名稱:
trait LoggedException extends Logger{ this: {def getMessage(): String} => def log(msg: String){ println(msg); } }
這個特質能夠被混入任何擁有getMessage方法返回值類型爲String的類