首页 > 代码库 > 快学SCALA(10)--特质
快学SCALA(10)--特质
当做接口使用的特质:
trait Logger { def log(msg:String) //抽象方法 } class ConsoleLogger extends Logger with Cloneable with Serializable{ def log(msg: String): Unit = {println(msg)} }
注:1. 在重写特质的抽象方法时不需要给出override关键字;
2. 如果需要的特质不止一个,可以使用with关键字来添加额外的特质
带有具体实现的特质:
trait Logger { def log(msg:String) //抽象方法 } trait ConsoleLogger{ def log(msg: String): Unit = {println(msg)} } class SavingAccounts extends Logger with ConsoleLogger{ var balance = 0: Double def withdraw(amount: Double): Unit = { if(amount > balance) log("Insufficient funds") else balance -= amount } }
在这个例子中,SavingsAccount从ConsoleLogger特质得到了一个具体的log方法实现。用JAVA接口的话,这是不可能的。我们说ConsoleLogger的功能被“混入”了SavingsAccount类。
注:但是让特质拥有具体行为存在一个弊端。当特质改变时,所有混入了该特质的类都必须重新编译。
带有特质的对象
可以在构建单个对象时添加特质,表示在构造对象的时候“混入”了更好的类,在这个时候优先执行构建对象时添加的特质内的方法。
叠加在一起的特质
可以为类或对象添加多个相互调用的特质,从最后一个开始。
trait Logger { def log(msg:String) { } } trait ConsoleLogger extends Logger{ override def log(msg: String): Unit = {println(msg)} } class SavingAccounts extends Logger with ConsoleLogger{ var balance = 0: Double def withdraw(amount: Double): Unit = { if(amount > balance) log("Insufficient funds") else balance -= amount } } trait TimestampLogger extends Logger{ override def log(msg: String): Unit = { super.log(new java.util.Date() + " " + msg) } } trait ShortLogger extends Logger{ val maxLength = 15 override def log(msg: String): Unit = { super.log(if(msg.length <= maxLength)msg else msg.substring(0, maxLength-3) + "...") } } object TestTrait { def main(args: Array[String]): Unit = { val acct1 = new SavingAccounts with ConsoleLogger with TimestampLogger with ShortLogger val acct2 = new SavingAccounts with ConsoleLogger with ShortLogger with TimestampLogger acct1.withdraw(1.0) acct2.withdraw(1.0) } }
执行TestTrait对象的main方法结果如下:
acct1首先执行ShortLogger的log方法,然后用super.log调用TimestampLogger
acct2正好相反
如果需要控制具体是哪一个特质的方法被调用,则可以在方括号中给出名称:super[ConsoleLogger].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 SavingAccounts extends Logger{ var balance = 0: Double def withdraw(amount: Double): Unit = { if(amount > balance) severe("Insufficient funds") else balance -= amount } override def log(msg: String): Unit = {println(msg)} }
特质中的具体字段
特质中的字段可以是具体的,也可以是抽象的。如果给出了初始值,那么字段就是具体的。
来自特质的字段被加入子类字段。
特质中的抽象字段
特质中未被初始化的字段在具体的子类中必须被重写
特质构造顺序
构造器将按照如下的顺序执行:
- 首先调用超类的构造器
- 特质构造器在超类构造器之后、类构造器之前执行
- 特质由左到右被构造
- 每个特质当中,父特质优先被构造
- 如果多个特质共有一个父特质,而那个父特质已经被构造,则不会被再次构造
- 所有特质构造完毕,子类被构造
举例来说,考虑如下一个类:
class SavingsAccount extends Account with FileLogger with ShortLogger
构造器将按照如下的顺序构造
- Account(超类)
- Logger(第一个特质的父特质)
- FileLogger(第一个特质)
- ShortLogger(第二个特质,注意此时它的父特质Logger已经被构造)
- SavingsAccount(类)
初始化特质中的字段
特质不能有构造器参数,这是特质与类的唯一技术差别
在特质中放置一个抽象字段,在子类的构造函数中对这个抽象字段进行初始化是不可行的:
trait FileLogger extends Logger{ val filename: String val out = new PrintStream(filename) override def log(msg: String): Unit = {out.println(msg); out.flush()} } object TestTrait { def main(args: Array[String]): Unit = { val acct = new SavingAccounts with FileLogger { override val filename: String = "myapp.log" } } }
在这种情况下,由于FileLogger优先于子类被构造(子类就是一个扩展自SavingAccounts,混入FileLogger的匿名类),故在对FileLogger的out字段初始化的时候会抛出空指针异常。
有两种解决方法:
1. 提前定义:能够解决问题,但不是很漂亮
class SavingsAccount extends {
val filename = "savings.log"
} with Account with FileLogger {
...
}
在FileLogger被构造的时候,filename已经是初始化过的了
2. 懒值
trait FileLogger extends Logger{ val filename: String lazy val out = new PrintStream(filename) override def log(msg: String): Unit = {out.println(msg); out.flush()} }
如此一来,out字段将在初次被使用时才会初始化。而在那个时候,filename字段应该已经被设好值了。不过,由于懒值在每次使用前都会检查是否已经初始化,它们用起来并不是那么高效。
快学SCALA(10)--特质