知识点
- 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 子类