scala中没有多重继承,scala提供特质而非接口,特质可以同时拥有具体方法和抽象方法,而类可以实现多个特质。
trait Equal {
def isEqual(x: Any): Boolean
def isNotEqual(x: Any): Boolean = !isEqual(x)
}
在重写特质的抽象方法时不需要使用 override
关键字。
如果你需要不止一个特质,可以用 with
关键字来添加额外的特质。
所有Java接口都可以作为scala特质来使用。
在scala中,特质的方法不一定需要是抽象的。
让特质拥有具体行为有一个弊端,当特质改变的时候,所有混入该特质的类都必须重新编译。
在构造单个对象时,可以给它添加特质。
val act = new SavingAccount with ConsoleLogger
首先定义特质 Logger
:
trait Logger {
def log(msg:String)
}
接下来定义特质 TimestampLogger
并继承重写 log
方法:
trait TimestampLogger extends Logger{
abstract override def log(msg:String){
super.log(new java.util.Date() + " " + msg)
}
}
特质中的字段可以是具体的,也可以是抽象的。如果给出了字段的初始值,那么该字段就是具体的。如果没有给出初始值,那么就是抽象的。抽象字段在具体的子类中必须被重写。重写的时候不需要使用 override
关键字。
和类一样,特质也有构造器,由字段的初始化和其他特质体中的语句构成。
构造器的执行顺序如下:
- 首先调用超类的构造器
- 特质构造器在超类构造器之后,类构造器之前执行
- 特质由左至右被构造
- 每个特质中,父特质首先被构造
- 如果多个特质共有一个父特质,而该父特质已经被构造,则不会再次构造
- 所有特质构造完,子类被构造
特质不能有构造参数,每个特质都有一个无参数的构造器。
初始化特质中的字段有两种方法:
- 提前定义。
val act = new {
val filename = "a.jpg"
} with SaveAccount with FileLogger
- 在FileLogger构造器中使用懒值:
trait FileLogger extends Logger{
val filename:String
lazy val out = new PrintStream(filename)
def log(msg:String) { out.println(msg)}
}
特质也可以扩展类,这个类会自动成为所有混入该特质的超类。
特质的超类也自动成为我们类的超类。
当特质以以下代码开始定义时:
this: 类型 =>
它便只能混入指定类型的子类
trait LoggedException extends Logged{
this:Exception =>
def log(){log(getMessage()}
}
注意该特质并不扩展 Exception
类,而是有一个自身类型 Exception
。这意味着,它只能被混入 Exception
子类。
在特质的方法中,我们可以调用该自身类型的任何方法。