快学Scala(10)--特质

Posted on 2017-04-02 18:35  paulingzhou  阅读(251)  评论(0编辑  收藏  举报

当做接口使用的特质:

trait Logger {
  def log(msg:String) //抽象方法
}


class ConsoleLogger extends Logger with Cloneable with Serializable{
 def log(msg: String): Unit = {println(msg)}
}

  注:1. 在重写特质的抽象方法时不需要给出override关键字;

    2. 如果需要的特质不止一个,可以使用with关键字来添加额外的特质

带有具体实现的特质:

trait Logger {
  def log(msg:String) //抽象方法
}


trait ConsoleLogger{
  def log(msg: String): Unit = {println(msg)}
}


class SavingAccounts extends Logger with ConsoleLogger{
  var balance = 0: Double

  def withdraw(amount: Double): Unit = {
    if(amount > balance) log("Insufficient funds")
    else balance -= amount
  }
}

  在这个例子中,SavingsAccount从ConsoleLogger特质得到了一个具体的log方法实现。用JAVA接口的话,这是不可能的。我们说ConsoleLogger的功能被“混入”了SavingsAccount类。

  注:但是让特质拥有具体行为存在一个弊端。当特质改变时,所有混入了该特质的类都必须重新编译。

带有特质的对象

可以在构建单个对象时添加特质,表示在构造对象的时候“混入”了更好的类,在这个时候优先执行构建对象时添加的特质内的方法。

叠加在一起的特质

可以为类或对象添加多个相互调用的特质,从最后一个开始。

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

trait ConsoleLogger extends Logger{
  override def log(msg: String): Unit = {println(msg)}
}

class SavingAccounts extends Logger with ConsoleLogger{

  var balance = 0: Double


  def withdraw(amount: Double): Unit = {
    if(amount > balance) log("Insufficient funds")
    else balance -= amount
  }
}

trait TimestampLogger extends Logger{
  override def log(msg: String): Unit = {
    super.log(new java.util.Date() + " " + msg)
  }
}

trait ShortLogger extends Logger{
  val maxLength = 15

  override def log(msg: String): Unit = {
    super.log(if(msg.length <= maxLength)msg else msg.substring(0, maxLength-3) + "...")
  }
}

object TestTrait {

  def main(args: Array[String]): Unit = {
    val acct1 = new SavingAccounts with ConsoleLogger with TimestampLogger with ShortLogger
    val acct2 = new SavingAccounts with ConsoleLogger with ShortLogger with TimestampLogger

    acct1.withdraw(1.0)
    acct2.withdraw(1.0)
  }


}

  执行TestTrait对象的main方法结果如下:

acct1首先执行ShortLogger的log方法,然后用super.log调用TimestampLogger

acct2正好相反

如果需要控制具体是哪一个特质的方法被调用,则可以在方括号中给出名称:super[ConsoleLogger].log(...)。这里给出的类型必须是直接超类型;你无法使用继承层级中更远的特质或类。

当做富接口使用的特质

trait Logger {
  def log(msg:String)
  def info(msg: String) {log("INFO: " + msg)}
  def warn(msg: String) {log("WARN: " + msg)}
  def severe(msg: String) {log("SEVERE: " + msg)}
}

class SavingAccounts extends Logger{

  var balance = 0: Double


  def withdraw(amount: Double): Unit = {
    if(amount > balance) severe("Insufficient funds")
    else balance -= amount
  }

  override def log(msg: String): Unit = {println(msg)}
}

  

特质中的具体字段

特质中的字段可以是具体的,也可以是抽象的。如果给出了初始值,那么字段就是具体的。

来自特质的字段被加入子类字段。

特质中的抽象字段

特质中未被初始化的字段在具体的子类中必须被重写

特质构造顺序

构造器将按照如下的顺序执行:

  1. 首先调用超类的构造器
  2. 特质构造器在超类构造器之后、类构造器之前执行
  3. 特质由左到右被构造
  4. 每个特质当中,父特质优先被构造
  5. 如果多个特质共有一个父特质,而那个父特质已经被构造,则不会被再次构造
  6. 所有特质构造完毕,子类被构造

举例来说,考虑如下一个类:

class SavingsAccount extends Account with FileLogger with ShortLogger

  构造器将按照如下的顺序构造

  1. Account(超类)
  2. Logger(第一个特质的父特质)
  3. FileLogger(第一个特质)
  4. ShortLogger(第二个特质,注意此时它的父特质Logger已经被构造)
  5. SavingsAccount(类)

初始化特质中的字段

特质不能有构造器参数,这是特质与类的唯一技术差别

在特质中放置一个抽象字段,在子类的构造函数中对这个抽象字段进行初始化是不可行的:

trait FileLogger extends Logger{

  val filename: String
  val out = new PrintStream(filename)

  override def log(msg: String): Unit = {out.println(msg); out.flush()}
}

object TestTrait {

  def main(args: Array[String]): Unit = {
    val acct = new SavingAccounts with FileLogger {
      override val filename: String = "myapp.log"
    }
  }

}

  在这种情况下,由于FileLogger优先于子类被构造(子类就是一个扩展自SavingAccounts,混入FileLogger的匿名类),故在对FileLogger的out字段初始化的时候会抛出空指针异常。

有两种解决方法:

1. 提前定义:能够解决问题,但不是很漂亮

class SavingsAccount extends {
  val filename = "savings.log"
} with Account with FileLogger {
...
}  

  在FileLogger被构造的时候,filename已经是初始化过的了

2. 懒值

trait FileLogger extends Logger{
  val filename: String
  lazy val out = new PrintStream(filename)
  override def log(msg: String): Unit = {out.println(msg); out.flush()}
}

  如此一来,out字段将在初次被使用时才会初始化。而在那个时候,filename字段应该已经被设好值了。不过,由于懒值在每次使用前都会检查是否已经初始化,它们用起来并不是那么高效。