Scala特质

  • 类可以实现任意数量的特质。
  • 特质可以要求实现它的类具备特定的字段,方法或超类。
  • 和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 !