为什么没有多重继承
Scala和Java一样不允许从多个超类继承。一开始,这听上去像是个很不幸的局限。为什么类就不能从多个类进行扩展呢?某些编程语言,特别是C++,允许多重继承--但是代价也是出人意料的高。
如果把毫不相干的类组装在一起,多重继承没有什么问题,但是如果这些类具有某些共同字段或方法,麻烦就来了。例如:
class Student {
private Integer id;
}
class Employee {
private Integer id;
}
// 假如我们有一个子类继承上面两个父类
class child extend Student,Employee {
// 此时该类中就继承了两个id, 那么我们在调用该类中id字段的时候根本就不知道是哪一个。
}
在Java中应对这种问题采用了非常强的限制策略。类只能扩展自一个超类,它可以实现任意数量的接口,但是接口只能包含抽象方法,不能包含字段。
Scala提供特质而非接口。特质可以同时拥有抽象方法和具体方法,而类可以实现多个特质。这个设计解决了Java接口的问题。
----
当做接口使用的特质
下面我们先用Scala定义一个特质
trait Logger{
// scala中不需要将方法声明为abstract , 特质中未被实现的方法默认就是抽象的
def log(msg:String) // 抽象方法
}
// 使用 extends 继承特质 ,和Java一样,Scala只能继承一个超类,但可以有任意数量的特质
// 如果需要的特质不止一个我们可以使用 with 关键字来增加额外的特质
class ConsoleLogger extends Logger with Cloneable with Serializable{
// 重写特质中抽象方法也不需要 override
def log(msg: String): Unit = println(msg)
}
带有具体实现的特质
在Scala中,特质中的方法并不一定就是抽象的。
trait ConsoleLogger {
def log(msg: String): Unit = println(msg)
}
// 继承 特质ConsoleLogger 可以直接使用特质中定义的具体方法
class saveAccount extends ConsoleLogger{
def withdraw() ={
log("do something")
}
}
上面例子中 saveAccount继承了特质ConsoleLogger,并且直接调用特质中的log方法,如果使用Java接口是无法实现的。
带有特质的对象
在构造单个对象的时候,你可以为它添加特质。
class SaveAccount extends Account with Logged {
def withdraw(amount:Double) ={
log("do something")
}
}
// 在类中定义一个特质,有一个什么都不做的具体方法,
trait Logged {
def log(msg:String) = {
}
}
上面例子中有可能日志不会被记录,但是我们可以在构造具体对象的会后混入一个日志的实现。这样就可以进行日志记录了。
val account = new SaveAccount with Logged
account.log("do something else")
在特质中重写抽像方法
下面这段代码在编译时就会报错,因为我们在Logger中定义的log方法是抽象方法没有具体实现。
trait TimeStampLogger extends Logger {
// 重写抽象方法
override def log(msg:String) = {
// 编译时报错
/**
* 报错信息
* Error:(43, 11) method log in trait Logger is accessed from super.
* It may not be abstract unless it is overridden by a member declared `abstract' and `override'
* super.log(new Date() + msg)
*/
super.log(new Date() + msg)
}
}
Scala会认为在TimeStampLogger中log()仍然是抽象的,所以需要添加 override 和 abstract 关键字。这样就不会报错。
trait TimeStampLogger extends Logger {
// 重写抽象方法
override abstract def log(msg:String) = {
super.log(new Date() + msg)
}
}
特质中的抽象字段、具体字段
特质中的字段可以是具体的也可以是抽象的,如果在定义的时候给了初始值那么就是具体的。
trait ShortLogger extends Logged {
val maxLength = 15 // 具体字段
}
混入该特质的类自动获得一个maxLength字段。通常,对于特质中的每一个具体字段,使用该特质的类都会获得一个字段与之对应。这些字段不是被继承的,它们只是简单地被加到子类当中。
那么为什么会被直接加到子类当中呢。在JVM中,一个类只能扩展一个超类,因此来自特质的字段不能以相同的方式继承,由于这个限制,maxLength字段被直接加到子类中。
特质中未被初始化的字段,在子类中必须被重写。
class SaveAccount extends Account with Logged with ShortLogger {
// 不需要加 override 关键字
val maxLength: Int = 20
}
特质构造顺序
特质构造器以如下顺序执行
1、首先调用超类的构造器
2、特质构造器在超类构造器调用之后、类构造器调用之前执行
3、特质从左到右被构造
4、每个特质当中,父特质先被构造
5、如果多个特质共有一个父特质,而那个父特质已经被构造,则不会再次被构造
6、所有特质构造完成,子类被构造
长按识别图中二维码
关注获取更多资讯