Scala快速入门-8-特质

Posted by Yezhiwei on January 7, 2018

知识点

  • Scala和Java一样不允许类继承多个超类,特质解决这一局限性
  • 类可以实现任意数量的特质
  • 当将多个特质叠加在一起时,顺序很重要,其方法先被执行的特质排在更后面
  • Scala特质可以提供方法和字段的实现
  • 特质要求实现它们的类具备特定的字段、方法或超类
  • 特质可以同时拥有抽象方法和具体方法,而类可以实现多个特质

当做接口使用的特质

  • Scala特质完全可以像Java的接口一样,使用关键字 trait
  • 不需要将方法声明为abstract,特质中未被实现的方法默认就是抽象的
  • 在子类中重写特质的抽象方法不需要用 override 关键字
package com.gemantic.base

/**
  * @author Yezhiwei
  * @date 18/1/6
  */
object TraitLearn {

  def main(args: Array[String]): Unit = {
    val logger = new ConsoleLogger

    logger.log("console log message...")
  }
}


trait Logger {
  def log(msg: String)
}


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

说明:

子类实现特质,用 extends 而不是 implements

不需要写 override

如果需要多个特质,可以用with关键字来添加额外的特质,如下代码

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

带有具体实现的特质

  • 在Scala的特质中的方法并不需要一定是抽象的
  • 子类从特质得到了一个具体的log方法实现
trait ConsoleLoggerImp {
  def log(msg: String) {println(msg)}
}

class AccountAction extends Account with ConsoleLoggerImp {

  def withdraw(amount: Double): Unit = {
    if (amount > nowBalance) {
      log("insufficient funds")
    } else {
      log("enough funds")
    }
  }
}

object TraitLearn {

  def main(args: Array[String]): Unit = {
    // 当做接口使用的特质
    val logger = new ConsoleLogger
    logger.log("console log message...")

    // 带有具体实现的特质
    val accountAction = new AccountAction
    accountAction.withdraw(1000)
  }
}

带有特质的对象

  • 在构造单个对象时,可以为它添加特质
  • 在定义子类时可以使用不做任何实现的特质,在构造具体对象的时候混入一个更合适的实现
  • 特质中重写抽象方法,必须在方法上使用 abstract 及 override
// 有默认实现,但是什么也没有做
trait Logged {
  def log(msg: String) {}
}

trait FileLogged extends Logged {
  override def log(msg: String): Unit = println("saving file : " + msg)
}

class AccountAction extends Account with Logged {

  def withdraw(amount: Double): Unit = {
    if (amount > nowBalance) {
      log("insufficient funds")
    } else {
      log("enough funds")
    }
  }
}

object TraitLearn {

  def main(args: Array[String]): Unit = {
    

    // 带有特质的对象,可以混入不同的日志
    val accountActionLogger = new AccountAction with FileLogged
    accountActionLogger.withdraw(1000)
  }
}

// 运行输出结果
saving file : insufficient funds

叠加在一起的特质

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

// 为日志增加时间戳
trait TimestampLogged extends Logged {
  override def log(msg: String): Unit = super.log(new java.util.Date() + " " +  msg)
}

// 如果日志内容长度超过10,截断
trait ShortLogged extends Logged {
  override def log(msg: String): Unit = super.log(if (msg.length <= 10) msg else msg.substring(0, 10) + "...")
}

object TraitLearn {

  def main(args: Array[String]): Unit = {

    // 带有特质的对象
    val accountActionLogger = new AccountAction with FileLogged with TimestampLogged with ShortLogged
    accountActionLogger.withdraw(1000)
    
    val accountActionLogger1 = new AccountAction with FileLogged with ShortLogged with TimestampLogged
    accountActionLogger1.withdraw(1000)
  }
}

// 输出结果为

saving file : Sat Jan 06 12:38:07 CST 2018 insufficie...
saving file : Sat Jan 06...

  • 注意上面的特质调用顺序及log方法每一个都将修改过的消息传递给supper.log

特质构造顺序

  • 和类一样,特质也可以有构造器,由字段的初始化和其他特质体中的语句构成
  • 构造器执行顺序

首先调用超类的构造器

特质构造器在超类构造器之后、类构造器之前执行

特质由左到右被构造

每个物质当中,父特质先被构造

如果多个特质共有一个父特质,而那个父特质已经被构造,则不会再次构造

所有的特质构造完毕,子类被构造

  • 示例
class AccountAction extends Account with FileLogged with ShortLogged {
	...
}

构造器执行顺序如下

超类 Account

Logged ,第一个特质的父特质

FileLogged 第一个特质

ShortLogged 第二个特质,它的父特质Logged已被构造

AccountAction 子类


敬请期待下一篇<高阶函数>~