特质 – trait
java、scala对多重继承的解决方法
— scala和java一样并不允许类从多个超类继承。c++提供多重继承。
— 为了实现这一个目标,java只允许扩展一个超类,它可以实现任意数量的接口,但接口只能包含抽象方法,不能包含字段。
— 如果你想调用其他方法来实现某些方法,在java接口中做不到,但是我们又只能扩展一个抽象基类,这样并不能很大程度上满足设计的要求。
— scala提供了“特质”而非接口,特质可以同时拥有抽象方法,而类可以实现多个特质。
特质— trait
- scala特质可以向java接口一样工作
trait Logger{
def log(msg : String)//抽象方法
}
class ConsoleLogger extends Logger{
def log(msg : String){//不需要override
println(msg)
}
}
我们可以用with关键字来添加额外的特质:
class ConsoleLogger extends Logger with Cloneable with Serializable
所有java接口都可以当做Scala特质来使用
- 特质也可以带有实现
trait ConsoleLogger{
def log(msg : String){println(msg)}
}
class Savings extends Acount with ConsoleLogger{
def withdraw(amount : Double){
if(amount > balance)
log("Insufficient funds")
else balance -= amount
}
}
注意Savings类从特质得到log方法实现。用java接口不能做到。
对象也可以带有特质
trait Logger{
def log(msg : String)//抽象方法
}
trait ConsoleLogger extends Logged{
override def log(msg : String){println(msg)}
}
class Savings extends Acount with Logger{
def withdraw(amount : Double){
if(amount > balance)
log("Insufficient funds")
else balance -= amount
}
}
如果我们想要更好的特质,比如ConsoleLogger(尽管功能一样),我们可以再定义对象时带上想要使用到的特质:
val acct = new Accounts with ConsoleLogger
//另一个对象也可以拥有不同特质
val acct2 = new Accounts with FileLogger
特质可以叠加
在前面看到,当我们使用一个特质的抽象方法时并不需要override,在实现一个具体的方法的时候需要override。
例:为日志信息添加时间戳
trait TimestampLogger extends Logged{
override def log(msg : String){
super.log(new java.util.Date() + " " + msg)
}
}
trait ShortLogger extends Logged{
override def log(msg : String){
super.log(if (msg.length <= maxLength) msg
else msg.subString(0,maxLength - 3) + "...")
}
}
super.log调用的是特制层级中的下一个特质,具体是哪一个根据特质的添加顺序来定。
super.log 并不像类中那样拥有相同含义,如果那样的话Logger里面的log方法什么也不做,我们无法从源码判断super.log会执行那里的方法。具体怎么调用下面说明。
val acct = new Accounts with ConsoleLogger with TimestampLogger with ShortLogger
val acct2 = new Accounts with ConsoleLogger with ShortLogger with TimestampLogger
这两个在ShortLogger和TimestampLogger上扩展顺序不同。导致
Sun Feb 06 17:45:45 ICT 2016 Insufficient…
Sun Feb 06 1…
从结果上可以看出来从何往前逐个执行。第二个在调用ShortLogger时信息被截断
特质中重写抽象方法
trait Logger{
def log(msg : String)//抽象方法
}
trait TimestampLogger extends Logged{
override def log(msg : String){//重写抽象方法
super.log(new java.util.Date() + " " + msg)
}
}
这块super.log(new java.util.Date() + ” ” + msg)是错误的,因为Logger.log方法并没有实现,也就是说TimestampLogger 的log方法依旧是抽象的,虽然向前面所说我们无法从源码判断super.log会执行那里的方法,依据混入·的顺序而定,但是我们这块得声明为abstract。
abstract override def log(msg : String){
super.log(new java.util.Date() + " " + msg)
}
在scala中特质和具体、抽象方法的使用十分普遍,在java中你需要定义一个接口和实现它的抽象类
特质中具体字段
抽象字段:没有具体值,给出了具体指字段就是具体的。
trait ShortLogger extends Logged{
val maxLength = 15
...
}
注意扩展了特制的类这些字段不是继承而是简单的被添加到了子类当中。
因为在JVM中一个类只能扩展一个超类,因此来自特质的字段不能继承到子类中,只能添加进去。
抽象字段
trait ShortLogger extends Logged{
val maxLength : Int //抽象字段
override def log(msg : String){
super.log(if (msg.length <= maxLength) msg
else msg.subString(0,maxLength - 3) + "...")
}
}
class Savings extends Acount with ConsoleLogger with ShortLogger{
val maxLength = 20 // 不需要写override
}
我们必须要在实现它的类中赋予给定的值。
特制的构造顺序
和类一样特质也可以有构造器,由字段初始化和其他特质体中的语句构成。
eg:
trait FileLogger extends Logger{
val out = new PrintWriter("app.log")//特质构造器一部分
out.println("# " + new Date().toString)//特质构造器一部分
def log(msg : String) {out.println(msg);out.flush()}
}
构造器执行顺序:
- 首先调用超类构造器
- 特质构造器在超类构造器之后,类构造器之前执行
- 特质由左到右被构造
- 每个特质中父特质先被构造
- 如果多个特质共有一个父特质,如果这个父特质已被构造则不会在构造
- 所有特质构造完毕,子类构造
初始化特质中的字段
特质构造器不能有参数,但是我们有时想指定一个特定的参数完成一个特定的情况,比如,我们想要指定日志文件:
val acct = new Savings with FileLogger("myapp.log")
但是,并不能这样来做
我们可以用前面方法:
trait FileLogger extends Logger{
val fileName : String //抽象字段
...
}
但是在用的时候我们需要注意:
val acct = new Savings with FileLogger(val fileName = "myapp.log")//这样并不对
val acct = new {val fileName = "myapp.log"} Savings with FileLogger("myapp.log")
需要提前定义
在使用类的时候在类中做同样的事情
class Savings extends {
val fileName = "myapp.log"
} with Account with FileLogger{
...
}
看一下我试验过后的代码
import java.io.PrintWriter
import java.util.Date
/**
* Created by duyang on 16/6/30.
*/
object Practice9 {
def main(args: Array[String]) {
//从结果可以看出super.log在调用时从右往左依次调用,特质在构造时从左往右依次构造
//但是在用抽象字段时,声明时要注意,要在最前面定义 而且用with连接
val acct = new {var maxLength = 10} with Savings with ConsoleLogger with shortLogger with TimestampLogger
acct.withDraw(10)
}
}
trait Logger1{
println("父特质")
val out = new PrintWriter("app.log")
out.println("# " + new Date().toString())
def log(msg : String){
}
}
trait ConsoleLogger extends Logger1{
println("第一个特质")//属于构造器
override def log(msg : String): Unit ={
println(msg)
}
}
//不带有抽象字段
//trait shortLogger extends Logger1{
// var maxLength = 10
// println("第二个特质")//属于构造器
// override def log(msg : String){
// super.log(if(msg.length <= maxLength) msg else msg.substring(0, maxLength - 3) + "...")
// }
//}
//带有抽象字段
trait shortLogger extends Logger1{
var maxLength : Int
println("第二个特质")//属于构造器
override def log(msg : String){
super.log(if(msg.length <= maxLength) msg else msg.substring(0, maxLength - 3) + "...")
}
}
trait TimestampLogger extends Logger1{
println("第三个特质")//属于构造器
override def log(msg : String): Unit ={
super.log(msg + " " + new java.util.Date())
}
}
class Account(){
var balance : Double = _
def this(balance : Double){
this()
this.balance = balance
println("我是超类")
}
}
class Savings extends Account with Logger1{
def withDraw(amount : Double): Unit ={
if(amount > balance){
log("Insufficient")
}else
balance -= amount
}
}
结果:
父特质
第一个特质
第二个特质
第三个特质
Insuffi...
自身类型
当特质扩展类时,编译器能确保一点,混入该特质的类都人这个类做超类。
当特质中以如下代码定义开始时:
this: 类型 =>
它便只能混入指定类型的子类
比如:
trait LoggedException extends Logged {
this : Exception =>
def log(){ log(getMessage())}
}
首先,该特质只能混入Exception类的子类,其次,可以使用该超类中的方法,比如: getMessage()
初学scala 有不对的地方可以互相讨论