所有的面向对象的语言都不允许直接的多重继承,因为会出现“deadlydiamond of death”问题。Scala提供了特质(trait),特质可以同时拥有抽象方法和具体方法,一个类可以实现多个特质。
当作接口使用的特质
特质中没有实现的方法就是抽象方法。类通过extends继承特质,通过with可以继承多个特质。
trait Logger {
def log(msg: String)
}
class ConsoleLogger extends Logger with Cloneable with Serializable {
def log(msg: String) {
println(msg)
}
}
Logger with Cloneable with Serializable是一个整体,extends这个整体
所有的java接口都可以当做Scala特质使用。
带有具体实现的特质
特质中的方法并不一定是抽象的:
trait ConsoleLogger {
def log(msg: String) {
println(msg)
}
}
class Account {
protected var balance = 0.0
}
class SavingsAccount extends Account with ConsoleLogger {
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
带有特质的对象,动态混入
在构建对象时混入某个具体的特质,覆盖掉抽象方法,提供具体实现
trait Logger {
def log(msg: String)
}
trait ConsoleLogger extends Logger {
def log(msg: String) {
println(msg)
}
}
class Account {
protected var balance = 0.0
}
abstract class SavingsAccount extends Account with Logger {
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
object Main extends App {
val account = new SavingsAccount with ConsoleLogger
account.withdraw(100)
}
叠加在一起的特质
super并不是指继承关系,而是指的加载顺序。继承多个相同父特质的类,会从右到左依次调用特质的方法。Super指的是继承特质左边的特质,从源码是无法判断super.method会执行哪里的方法,如果想要调用具体特质的方法,可以指定:super[ConsoleLogger].log(…).其中的泛型必须是该特质的直接超类类型(执行顺序从右向左)
trait Logger {
def log(msg: String);
}
trait ConsoleLogger extends Logger {
def log(msg: String) {
println(msg)
}
}
trait TimestampLogger extends ConsoleLogger {
override def log(msg: String) {
super.log(new java.util.Date() + " " + msg)
}
}
trait ShortLogger extends ConsoleLogger {
override def log(msg: String) {
super.log(if (msg.length <= 15) msg else s"${msg.substring(0, 12)}...")
}
}
class Account {
protected var balance = 0.0
}
abstract class SavingsAccount extends Account with Logger {
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
object Main extends App {
val acct1 = new SavingsAccount with TimestampLogger with ShortLogger
val acct2 = new SavingsAccount with ShortLogger with TimestampLogger
acct1.withdraw(100)
acct2.withdraw(100)
}
在特质中重写抽象方法
trait Logger2 {
def log(msg: String)
}
//因为有super,Scala认为log还是一个抽象方法
trait TimestampLogger2 extends Logger2 {
abstract override def log(msg: String) {
super.log(new java.util.Date() + " " + msg)
}
}
trait ShortLogger2 extends Logger2 {
abstract override def log(msg: String) {
super.log(if (msg.length <= 15) msg else s"${msg.substring(0, 12)}...")
}
}
trait ConsoleLogger2 extends Logger2 {
override def log(msg: String) {
println(msg)
}
}
class Account2 {
protected var balance = 0.0
}
abstract class SavingsAccount2 extends Account2 with Logger2 {
def withdraw(amount: Double) {
if (amount > balance) log("余额不足")
else balance -= amount
}
}
object Main2 extends App {
//这里可以根据上面的知识点理解此处
val acct1 = new SavingsAccount2 with ConsoleLogger2 with TimestampLogger2 with ShortLogger2
acct1.withdraw(100)
}
特质中的具体字段
特质中可以定义具体字段,如果初始化了就是具体字段,如果不初始化就是抽象字段。混入该特质的类就具有了该字段,字段不是继承,而是简单的加入类。是自己的字段。
特质中的抽象字段
特质中未被初始化的字段在具体的子类中必须被重写
特质构造顺序
在特质中如果有定义变量,那么一定在使用的时候提前定义,不然会出现空的情况
特质也是有构造器的,构造器中的内容由“字段的初始化”和一些其他语句构成
1、调用当前类的超类构造器
2、第一个特质的父特质构造器
3、第一个特质构造器
4、第二个特质构造器的父特质构造器由于已经执行完成,所以不再执行
5、第二个特质构造器
6、当前类构造器
特质不能有构造器参数,每个特质都有一个无参数的构造器。缺少构造器参数是特质与类之间唯一的技术差别。除此之外,特质可以具备类的所有特性,比如具体的和抽象的字段,以及超类。
1、特质可以继承自类,以用来拓展该类的一些功能
2、所有混入该特质的类,会自动成为那个特质所继承的超类的子类
3、如果混入该特质的类,已经继承了另一个类,不就矛盾了?注意,只要继承的那个类是特质超类的子类即可。
自身类型
主要是为了解决特质的循环依赖问题,同时可以确保特质在不扩展某个类的情况下,依然可以做到限制混入该特质的类的类型。比如:
//自身类型特质
trait Logger9{
this: Exception =>
def log(): Unit ={
println(getMessage)
}
}