- 类可以实现任意数量的特质。
- 特质可以要求实现它的类具备特定的字段,方法或超类。
- 和java接口不同,scala特质可以提供方法和字段的实现。
- 当你将多个特质叠加在一起时,顺序很重要,其方法先被执行的特质排在更后面。
多重继承
在c++中,采用虚基类来解决菱形问题,而java则采取更加强硬的限制策略,即类智能扩展自一个超类,但可以实现任意数量的接口,而接口只能包含抽象方法,不能包含字段。 scala提供特质而不是接口,特质可以同时拥有抽象方法和具体方法。和java一样,scala类只能有一个超类,但可以有任意数量的特质。
带有具体实现的特质
trait ConsoleLogger{
def log (msg : String){
println(msg)
}
}
class SavingAccount extends Account with ConsoleLogger{
def withdraw(amount : Double){
if(amount > balance) log("Insufficient funds")
else balance -= amount
}
}
带有特质的对象
在构造单个对象时,可以为它添加特质,同类的不同对象可以添加不同的特质。
trait Logged{
def log(msg : String){}
}
trait ConsoleLogger extends Logged{
def log (msg : String){
println(msg)
}
}
class SavingAccount extends Account with Logged{
def withdraw(amount : Double){
if(amount > balance) log("Insufficient funds")
else balance -= amount
}
}
//现在什么都不会被记录到日志。但你可以在构造具体对象时混入更好的日志记录器。
val acct1 = new SavingAccount with ConsoleLogger
//如果acct1调用log方法,会执行ConsoleLogger的log方法。
val acct2 = new SavingAccount with FileLogger
多个特质的叠加
你可以为类和对象添加多个互相调用的特质,从最后一个开始。对于需要分阶段加工处理某个值的场景非常有用。实际上,哪个函数被调用是由特质添加的顺序来决定的。
trait Logged{
def log(msg : String){}
}
trait TimestampLogger extends Logged{
override def log (msg : String){
super.log(new java.util.Date()+" "+ msg)
}
}
trait TimestampLogger extends Logged{
val maxLength = 15
override def log (msg : String){
super.log(
if (msg.length <= maxLength) msg
else msg.substring(0,maxLength -3)+"...")
}
}
val acct1 = new SavingAccount with ConsoleLogger with TimestampLogger with ShortLogger
//ShortLogger的log方法首先执行,然后它的super.log调用TimestampLogger
val acct2 = new SavingAccount with ConsoleLogger with ShortLogger with TimestampLogger
在特质中重写抽象方法
trait Logger{
def log(msg: String)
}
trait TimestampLogger extends Logger{
override def log (msg : String){
super.log(new java.util.Date()+" "+ msg)
}
}
//编译器会将super.log标记为错误,因为super.log并没实现。scala认为TimestampLogger依旧是抽象的,它需要混入一个具体的log方法,因此,你必须为方法打上abstract关键字和override关键字:
abstract override def log (msg : String){
super.log(new java.util.Date()+" "+ msg)
}
当做富接口使用的特质
特质可以包含大量工具方法,而这些工具方法可以依赖一些抽象方法来实现。
trait Logger{
def log(msg : String)
def info(msg: String){log("INFO : "+msg)}
def warn(msg: String){log("WARN: "+msg)}
}
class SavingAccount extends Account with Logger{
def withdraw(amount : Double){
if(amount > balance) log("Insufficient funds")
else balance -= amount
}
override def log(msg : String){println(msg)}
}
注意我们是怎么把抽象方法和具体方法结合在一起的。这样使用Logger特质的类就可以任意调用这些日志消息方法了。在scala中像这样在特质中使用具体和抽象方法十分普遍。
特质中的字段
特质中的字段可以使具体的也可以是抽象的。如果你给出了初始值,那么字段就是具体的。在JVM中,一个类智能扩展一个超类,因此来自特质的字段不能以超类相同的方式继承,你可以把具体的特质字段当做是针对使用该特质的类的“装配指令”,任何通过这种方式混入的字段都自动成为该类自己的字段。
trait TimestampLogger extends Logged{
val maxLength = 15
override def log (msg : String){
super.log(
if (msg.length <= maxLength) msg
else msg.substring(0,maxLength -3)+"...")
}
}
特质中为被初始化的字段在具体的子类中必须被重写。
trait TimestampLogger extends Logged{
val maxLength :Int
override def log (msg : String){
super.log(
if (msg.length <= maxLength) msg
else msg.substring(0,maxLength -3)+"...")
}
}
val acct = new SavingAccount with ConsoleLogger with TimestampLogger{
val maxLength = 15
}
特质的构造顺序
- 首先调用超类的构造器。
- 特质构造器在超类构造器之后,类构造器之前。
- 特质由左到右构造。
- 每个特质中,父特质先被构造。
- 如果多个特质共有一个父特质,而那个父特质已经被构造,则不会被再次构造。
- 所有特质构造完毕,子类被构造。
扩展类的特质
特质也可以扩展类,这个类会自动成为所有混入该特质的超类。如果我们的类已经扩展了一个超类,那么要求这个超类是特质的超类的子类,否则,不能混入该特质。
trait LoggedException extends Exception with Logged{
def log(){
log(getMessage())
}
}
背后发生了什么
scala需要把特质翻译成JVM的类和接口。 只有抽象方法的特质被简单的变成一个java接口。 如果特质有具体的方法,scala会帮我们创建一个伴生类,该伴生类用静态方法存放特质的方法。
Comments !