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