10 特質

  • 類可以實現任意數量的特質
  • 特質可以要求實現他們的類具備特定的字段、方法或超類
  • 特質可以提供方法和字段的實現
  • 多個特質疊加時,後面的特質其方法先被執行

特質

  • 同時擁有抽象方法和具體方法,以及狀態
  • 類可以實現多個特質
  • 特質中未被實現的方法默認就是抽象的
  • 繼承特質使用extends而不是implements
  • 多個特質使用with
  • 所有的Java接口都可以作爲Scala特質使用
  • extends後面的Logger with Cloneable with Serializable 可以看做一個整體,然後再由類擴展
trait Logger{
  //抽象方法,不提供具體實現,可以加abstract也可以不加
  def log(msg:String)
}

class ConsoleLogger extends Logger with Cloneable with Serializable {
  //實現抽象方法可以加override也可以不加
  override def log(msg: String): Unit = {
    println(msg)
  }
}

帶有具體實現的特質

  • 特質中的方法不一定是抽象
trait ConsoleLogger{
  def log(msg:String): Unit ={
    println(msg)
  }
}

class SavingsAccount extends Account with ConsoleLogger{
  def withdraw(amount:Double): Unit ={
    if (amount > balance) log("Insufficient funds")
    else balance -= amount
  }
}

class Account {
  val id = Account.newUniqueNumber()
  protected var balance = 0.0

  def deposit(amount: Double): Unit = {
    balance += amount
  }
  def CXYE={
    println("餘額:"+balance)
  }
}
object Account{
  private var lastNumber = 0
  private def newUniqueNumber():Int={
    lastNumber+=1
    lastNumber
  }
}

帶有特質的對象

  • 構造對象時可以添加特質
  • 一個抽象類不能被實例化,不管是否包含抽象字段或方法。如果抽象類的所有字段方法都是具體的,並且擴展了一個特質,那麼這個抽象類就能實例化爲帶特質的對象
abstract class A {val a="sr"}
trait B
new A with B
// res1: A with B = $anon$1@3f1158ee
trait Logger{
  def log(msg:String)
}
trait ConsoleLogger extends Logger{
  def log(msg:String): Unit ={
    println(msg)
  }
}
//現在SavingsAccount可以被實例化了
//val c = new SavingsAccount with ConsoleLogger
abstract class SavingsAccount extends Account with Logger{
    def withdraw(amount:Double): Unit ={
      if (amount > balance) log("Insufficient funds")
      else balance -= amount
    }
}

class Account {
  val id = Account.newUniqueNumber()
  protected var balance = 0.0

  def deposit(amount: Double): Unit = {
    balance += amount
  }
  def CXYE={
    println("餘額:"+balance)
  }
}
object Account{
  private var lastNumber = 0
  private def newUniqueNumber():Int={
    lastNumber+=1
    lastNumber
  }
}

疊加在一起的特質

  • 最後出現的方法先被調用,其次往前推進,最前面的方法最後被調用
  • 對於acc1,ShortLogger的log方法先被執行,“Insufficient funds"被截斷後變爲了"Insufficient”,然後super.log調用TimestampLogger的log方法將"Insufficient"加上時間戳,最後是ConsoleLogger的log方法將加上時間戳後的字符串打印到屏幕
  • 對於acc2,TimestampLogger的log方法先被執行,然後super.log調用的是ShortLogger
trait Logger{
  //抽象方法,不提供具體實現,可以加abstract也可以不加
  def log(msg:String)
}
trait ConsoleLogger extends Logger{
  def log(msg:String): Unit ={
    println(msg)
  }
}
trait TimestampLogger extends ConsoleLogger{
  println("LongLogger")
  override def log(msg: String): Unit = super.log(s"${java.time.Instant.now()} ${msg}")
}
trait ShortLogger extends ConsoleLogger{
  println("ShortLogger")
  override def log(msg: String): Unit = super.log(if(msg.length<=15) msg else s"${msg.substring(0,12)}")
}
abstract class SavingsAccount extends Account with Logger{
    def withdraw(amount:Double): Unit ={
      if (amount > balance) log("Insufficient funds")
      else balance -= amount
    }
}

val acc1 = new SavingsAccount with TimestampLogger with ShortLogger
acc1.withdraw(1000)
//LongLogger
//ShortLogger
//2019-12-15T04:04:04.337Z Insufficient
val acc2 = new SavingsAccount with ShortLogger with TimestampLogger
acc2.withdraw(1000)
//ShortLogger
//LongLogger
//2019-12-15T0
  • 控制具體哪個特質的方法被調用,使用super[特質].log(…),給出的類型必須是直接超類,不能是更遠的特質或類
  • 例如,在ShortLogger中調用Super[ConsoleLogger].log,會直接調用ConsoleLogger的log方法,而不是先TimestampLogger再ConsoleLogger
super[ConsoleLogger].log(if(msg.length<=15) msg else s"${msg.substring(0,12)}")

特質中重寫抽象方法

  • abstract override def log(msg:String)

當做富接口使用的特質

  • 依賴抽象方法實現一些方法
trait Logger{
  //抽象方法,不提供具體實現,可以加abstract也可以不加
  def log(msg:String)
  def info(msg:String)={log(s"INFO: ${msg}")}
  def warn(msg:String)={log(s"WARN: ${msg}")}
  def severe(msg:String)={log(s"SEVERE: ${msg}")}
}

abstract class SavingsAccount extends Account with Logger{
    def withdraw(amount:Double): Unit ={
      if (amount > balance) severe("Insuffient funds")
      else balance -= amount
    }
}

特質中的具體字段

  • 特質中的字段可以是具體的也可以是抽象的,取決於你是否給出初始值
  • 使用該特質的類會獲得一個字段與之對應,這些字段不能被繼承,只是簡單的被添加到了子類中
  • 在JVM中一個類只能擴展一個超類,因此當繼承自特質的時候,特質的字段不能以相同的方式繼承
  • maxLength書上說不能繼承,這裏也能繼承,不知道怎麼回事
class SavingsAccount extends Account with ConsoleLogger with ShortLogger {
  balance = 0.01
  override val maxLength=20
  println(maxLength)
    def withdraw(amount:Double): Unit ={
      if (amount > balance) log("Insuffient funds")
      else balance -= amount
    }
}
trait Logger{
  //抽象方法,不提供具體實現,可以加abstract也可以不加
  def log(msg:String)
  def info(msg:String)={log(s"INFO: ${msg}")}
  def warn(msg:String)={log(s"WARN: ${msg}")}
  def severe(msg:String)={log(s"SEVERE: ${msg}")}
}
trait ConsoleLogger extends Logger{
  println("ConsoleLogger")
  def log(msg:String): Unit ={
    println(msg)
  }
}
trait TimestampLogger extends ConsoleLogger{
  println("LongLogger")
  override def log(msg: String): Unit = super.log(s"${java.time.Instant.now()} ${msg}")
}
trait ShortLogger extends ConsoleLogger{
  val maxLength = 15
  println("ShortLogger")
  override def log(msg: String): Unit = super[ConsoleLogger].log(if(msg.length<=maxLength) msg else s"${msg.substring(0,maxLength)}")
}


class Account {
  val id = Account.newUniqueNumber()
  protected var balance = 0.0

  def deposit(amount: Double): Unit = {
    balance += amount
  }
  def CXYE={
    println("餘額:"+balance)
  }
}
object Account{
  private var lastNumber = 0
  private def newUniqueNumber():Int={
    lastNumber+=1
    lastNumber
  }
}

特質中的抽象字段

  • 未被初始化的字段在具體子類中必須被重寫,跟抽象類一樣

特質的構造順序

  • 特質也可以有構造器,由字段的初始化和其他特質體中的語句構成
  1. 首先調用超類的構造器
  2. 特質的構造器在超類的構造器之後、類構造器之前執行
  3. 特質由左到右被構造
  4. 在每個特質中,父特質先被構造
  5. 如果一個特質共有一個父特質,而那個特質已經被構造,那麼這個父特質不會被再次構造
  6. 所有特質構造完畢,子類被構造
  • 總結起來就是,從左到右,從祖宗到子孫的順序。SavingAccount是最子孫的,最後被構造。
  • 對於下面的一個類,其構造順序如下
  1. Account:超類
  2. Logger: 第一個特質的父特質ConsoleLogger的父特質,爺爺特質
  3. ConsoleLogger: 第一個特質的父特質
  4. TimestampLogger:第一個特質
  5. ShortLogger:第二個特質,其父特質和爺爺特質都已被構造,不再被構造了
  6. SavingAccount:類
class SavingAccount extends  Account with TimestampLogger with ShortLogger

初始化特質中的字段

  • 特質不能有構造器參數,每個特質都有一個無參數的構造器
  • 缺少構造器參數是特質與類之間的唯一技術差別
  • 初始化特質的字段得使用提前定義或懶值,
trait FileLogger extends Logger{
  val filename:String
  val out = new java.io.PrintStream(filename)
  def log(msg:String)={
    out.println(msg)
    out.flush()
  }
}
//下面的方法行不通,因爲FileLogger構造器先於子類構造器,子類沒構造之前就已經拋出了空指針異常,out那一句
//java.lang.NullPointerException
val acct = new SavingsAccount with FileLogger{
  val filename = "mylog.log"
}

//正確的方法,使用提前定義,注意兩個with,帶有特質的對象
val acct = new {val filename = "myapp.log"} with SavingAccount with FileLogger {
  log("hahaha")
}
//在類中,extends後跟預定義塊
class SavingAccount extends {val filename=“mylog.log”} with Account with FileLogger{
  //類的具體實現
}

擴展類的特質

  • 類也可以擴展特質
  • 特質的超類是任何混入該特質的類的超類
  • 類A擴展自B和特質C,如果C有超類D,那麼B是D的子類纔可以,如果B和D不相關,那麼就不能擴展了。

自身類型

  • 自身類型:如果特質的定義開頭是this:類型A=>,它只能被混入指定類型A的子類,只能類型A的子類能擴展這個特質。
  • 結構類型:給出類必須有的方法,而不是類的名稱,類定義的開頭是this:{def 方法F}=>,這個特質可以被混入任何擁有方法F的類。

特質的背後發生了什麼

  • 只有抽象方法的特質會被簡單地變成java接口
  • 有具體方法的時候就變成抽象類了

trait Logger2{
  val maxLength = 15 //具體字段
  val minLength :Int //抽象字段
  def log(msg:String) //抽象方法
  def log2(msg:String)=println(msg) //具體方法
}

class Account2 extends Logger2{
  val minLength=0
  def log(msg:String)="aa"
}
//查看字節碼
public class Account implements Logger2 {
  private final int minLength;
  private final int maxLength;
  public int maxLength();
  public void Logger2$_setter_$maxLength_$eq(int);
  public void log2(java.lang.String);
  public int minLength();
  public void log(java.lang.String);
  public Account();
}

public abstract class Logger2$class {
  public static void log2(Logger2, java.lang.String);
  public static void $init$(Logger2);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章