在scala中,特质既可以包含抽象方法也可以包含具体方法;比起java中的接口,它更像是类。
特质的几种用法
trait Logged{
def log(msg:String) //这是一个抽象方法
}
class ConsoleLogger extends Logger{//ConsoleLogger是特质Logger的子类
def log(msg:String){println(msg)} //注意这里不需要写override
}
使用with来添加额外的特质,一个类只可以拥有一个超类,但可以拥有任意特质
class ConsoleLogger extends Logger with Cloneable with Serializable
trait ConsoleLogger{
def log(msg:String){ println (msg)}
}
//Account是超类,ConsoleLogger是添加的特质
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{
override def log(msg:String){ println(msg)}
}
class SavingAccount extends Account with Logged{
def withdraw(amount:Double){
if(amount>balance) log("Insufficient funds")
else ...
}
...
}
val acct=new SavingAccount with ConsoleLogger
//特质ConsoleLogger 扩展了 特质Loggerd,并重写了方法log.
//单纯地调用SavingAccount时,log方法不会做任何事。(Logged中的log空)
//但在构造对象时混入了ConsoleLogger,改变了这个对象里的log方法。
叠加在一起的特质
特质可以互相扩展,可以为类或对象添加多个互相调用的特质。
调用顺序一般来说从最后一个特质开始处理。具体的可以参考构造顺序。
在特质中重写抽象方法
trait Logger{
def log(msg:String)
}
trait TimestampLogger extends Logger{
override def log(msg:String){
super.log(new java.util.Date()+" "+msg)
}
}
//这里试图像类扩展类一样重写抽象方法,但super.log无法编译,因为他是抽象的(Logger.log是抽象的,所以我们需要加上abstract)
trait TimestampLogger extends Logger{
abstract override def log(msg:String){
super.log(new java.util.Date()+" "+msg)
}
}
//在调用时我们仍需要混入一个具体的log方法。
trait Logger{
def log(msg:String)
def info(msg:String){log("INFO: "+msg)}//在一个具体方法里调用了上面的抽象方法
def warn(msg:String){log("WARN: "+msg)}
def severe(msg:String){log("SEVERE: "+msg)}
}
class SavingAccount extends Account with Logger{
def withdraw(amount:Double){
if (amount>balance) severe("Insufficient funds")
else ...
}
...
override def log(msg:String){println(msg);}
}
//在这里,info.war.severe三个方法均依赖log抽象方法实现;
给出了初始值的字段就是具体字段,当特质被添加至一个类或对象里时,该字段被装备到这个类或这个对象里。
trait ShortLogger extends logged{
val maxLength:Int
override def log(msg:String){
super.log(
if(msg.length<=maxLength) msg.substring(0,maxLength-3)+"...")
}
...
}
特质中未被初始化的字段就是抽象字段,当在一个具体的类或对象中使用该特质时,需要提供字段
val acc=new SavingAccount with ConsoleLogger with ShortLogger{ val maxLength=20}
class SavingAccount extends Account with ConsoleLogger with ShortLogger{
val maxLength=20
...
}
class SavingAccount extends Account with TimestampLogger with ShortLogger
//构造顺序:Account-Logged-TimestampLogger-ShortLogger-SavingAccount
trait FileLogger extends Logger{
val filename:String
val out=new PrintStream(filename)
def log(msg:String){ out.println(msg); out.flush()}
}
val acct = new{
val filename="myapp.log"
}with SavingAccount with FileLogger
trait LoggedException extends Exception with Logged{
def log() { log( getMessage() ) } //log方法调用了从Exception继承下来的getMessage()方法
}
class UnhappyException extends LoggedException{
override def getMessage()="arggh!"
}
//UnhappyException扩展至一个特质,LoggedException的超类自动编程UnhappyException的超类,所以它可以调用getMessage().
trait LoggedException extends Logged{
this:Exception=>
def log() { log(getMessage() ) }
}
//这个特质没有扩展Exception类,但有一个自身类型Exception.所以他只能被混入Exception的子类。在该特质中调用getMessage()是合法的,因为这个特质this一定是Exception。
trait LoggedException extends Logged{
this: { def getMessage(): String } =>
def log() { log( getMessage() )}
}
//这个特质可以被混入任何拥有getMessage()方法的类
总结:
特质的加入使得scala的面向对象的应用空前灵活,我们可以很有逻辑的增加方法或修改方法,而不至于使得结构紊乱。希望在以后的练习中可以更加熟悉特质的用法。