Scala學習(十)特質

Scala提供「特質」而非接口。特質能夠同時擁有抽象方法和具體方法,而類能夠實現多個特質。java

1.看成接口使用的特質

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

2.帶有具體實現的特質

在Scala中,特質中的方法並不須要必定是抽象的。舉例來講,咱們能夠把咱們的具體類變成一個特質:scala

trait Logger{  
    def log(msg: String){ println(msg) }  
}

如何使用:日誌

class ConsoleLogger extends Logger{
    def write(msg: String){ log("Hello World")}  
}

3.帶有特質的對象

首先,特質也是能夠被奇特特質擴展的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 

4.疊加在一塊兒的特質

//日誌特質
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被截斷

5.在特質中重寫抽象方法

//日誌特質
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) 

    }
}

6.當作富藉口是用的特質

特質包含了大量的工具方法,而這些工具方法能夠依賴一些抽象方法來實現。

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); }

}

7.特質中的具體字段

特質中的字段能夠試具體的能夠試抽象額,若是給了初始值,name字段就是具體的。

一般,對於特質中每個具體字段,使用該特製的類會得到一個字段預支對應。這些字段不是被繼承的;他們只是簡單的被加入到了子類當中。

在JVM中,一個類智能擴展一個超類,所以來自特質的字段不能以相同的方式繼承。因爲這個限制,特質中的字段被接入到了子類當中,並不是父類字段的繼承。

8.特質構造順序

構造器以以下順序執行:

  • 首先調用超累的構造器。
  • 特質構造器在超累構造以後、類構造器以前執行。
  • 特質由左到右被構造。
  • 每一個特質當中,父特質先被構造。
  • 若是多個特質共有一個父特質,而那個父特質已經被構造,則不會再次構造
  • 全部特質構造完畢,子類被構造。

例如:

class SavingAccount extends Account with FileLogger with ShortLogger

構造器將按照以下的順序執行:

  1. Account(超類)
  2. Logger(第一個特質的父特質)
  3. FileLogger(第一個特質)
  4. ShortLogger(第二個特質)。注意他的父特質L噢GG而已被構造
  5. SavingsAccount(類)

9.初始化特質中的字段

特質不能有構造器參數。每一個特質都有一個無參數的構造器。

說明:缺乏構造器參數是特質與類之間惟一的技術差異

提早定義:

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()}
}

10.擴展類的特質

一種不常見的用法是,特質也能夠擴展類。這個被特質擴展的類將會自動成爲全部混入該特質的超類。

假定有以下代碼:

trait LoggedException extends Exception with Logged{
    def log() { log(getMessage()) }
}

class UnhappyException extends LoggedException{

   ...

}

那麼UnhappyException的超類自動變成LoggedException特質的擴展類Exception

若是咱們的類已經擴展了另外一個類怎麼辦?不要緊,只要是特質的超類的一個子類便可。若是咱們的類擴展了一個不相關的類,那麼就不可能混入這個特質了。

11.自身類型

當特質擴展類時,編譯器可以確保的一件事是全部混入該特質的類都認爲這個類做超類。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的類

相關文章
相關標籤/搜索