在scala中,特质既可以包含抽象方法也可以包含具体方法;比起java中的接口,它更像是类。
- 仅含有抽象方法的特质(看作接口)可以被一个类扩展(extends),在子类中实现该抽象方法(不需要override);
- 含有具体实现的特质可以被“混入”(with)一个类,相当于特质的具体方法被混入了那个类中,可以直接调用;
- 在构造对象时,特质可以被“混入”(with)这个对象,即在对象上可以调用该特质中的方法;
- 可以为一个类或对象添加多种特质,从最后一个开始处理;
- 特质可以被另一个特质扩展(即继承 extends),前者中的抽象方法可以被后者重写(需要加 abstract override);
- 特质可以包含大量的方法,它可以是具体的也可以是抽象的;当这个特质被添加(with)时,可以直接调用他的具体方法(第2点)或重写他的抽象方法(override)。
- 特质中的具体字段被简单地“添加到”子类中,并非继承。对于特质中的抽象字段,在使用该特质(具体的类或构造具体对象时),需要先提供字段。
- 特质没有构造器参数,要初始化需要在构造前提前定义。
- 特质可以扩展类。特质的超类自动成为任何混入该特质的类的超类。
- 含有自身类型(this:=》type)的特质只能被混入这种类(给出类名)。含有结构类型(this: { def xxxx():xxx}=>)(给出类必须拥有的方法)的特质只能被混入拥有这种方法的类。
特质的几种用法
- 当作接口使用的特质
与接口作用相同,无具体实现的方法;可以在子类中实现log方法
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的面向对象的应用空前灵活,我们可以很有逻辑的增加方法或修改方法,而不至于使得结构紊乱。希望在以后的练习中可以更加熟悉特质的用法。