本文主要对Scala中特质的概念与使用进行介绍
特质是Scala里面代码复用的基础单元。
与 Python 不同,Python 子类可以继承自多个父类,而 Scala 不允许一个类从从个超类继承,只能继承唯一的超类。
但是 Scala 允许一个类混入任意数量的特质,混入就是指类使用了特质提供的方法。
那么特质在 Scala 中是如何定义的呢?
类的定义使用关键字 class,特质的定义使用关键字 trait。
Scala 中的特质既可以有普通字段和普通方法也可以有抽象字段和抽象方法。如果特质中只有抽象内容,叫做瘦接口;如果既有抽象内容也有具体内容,叫做富接口。
// 特质t的定义
trait t {
// 普通方法
def p(){
println("i am method p in trait a")
}
// 特质中未被实现的方法默认就是抽象方法,不需要关键字修饰
def ap()
// 普通字段
val sn = "simple"
// 抽象字段
var an
}
// 特质A混入类A
// 混入一个特质使用 extends 关键字
// 混入多个特质则是 extends trait_a with trait_b with ...
class A extends t {
// 类A自带的方法
def P(){
println("i am method P in class A")}
// 重写特质中有具体实现的方法 用override 关键字
override def p() {
println(" override by A")
}
// 重写特质中的抽象方法 不需要override关键字
def ap() {
println("do not need override keywords")
}
// 类A自带的字段
val asn = "asimple"
// 重新抽象字段
var an = "an"
}
混入特质的类重写特质的普通方法需要 override 关键字,重写抽象方法不需要 override 关键字。特质中的抽象方法在类中必须被重写。
混入特质的类会自动获得特质的普通字段,以上述代码为例,为sn
字段,但是这些字段不是被继承的,只是简单的被加入到了子类当中,是该类自己的字段,与asn
字段地位相同。而特质中的抽象字段在子类中的必须被重写,不需要 override 关键字。
带特质的对象
有时候我们希望在不改变类继承体系的情况下,对类对象的功能进行临时增强或者扩展
var/val 对象名 = new 类名 with 特质名
object test {
trait MessageSender{
def send(msg: String) = println(s"$msg")
}
class MessageWorker {
}
def main(args: Array[String]): Unit = {
val a = new MessageWorker with MessageSender
// a对象可以调用特质中的 send 方法
a.send("send method in MessageSender")
}
}
特质的构造
和类的主构造器不同,特质的构造器不能有参数。
构造器按照如下的顺序执行:
- 首先调用超类的构造器
- 特质从左向右被构造
- 每个特质中,父特质先被构造
- 如果多个特质共有一个父特质,而那个父特质已经被构造,则不会被再次构造
- 所有特质构造完毕,子类被构造
以下面代码为例
object test{
trait a{
println("trait a")
}
trait b extends a{
println("trait b")
}
trait c extends a{
println("trait c")
}
class d {
println("class d")
}
class e extends d with c with b{
println("class e")
}
def main(args: Array[String]): Unit = {
val E = new e
}
}
// 打印结果为
class d
trait a
trait c
trait b
class e
叠加特质
可以为类或对象添加多个互相调用( 调用顺序 从右向左 )的特质,对于需要分阶段加工处理某个值的场景很有用。
object test{
trait ML{
def model(data: String)={
println("选择模型及超参数")
println("finish model")
}
}
trait FE extends ML {
override def model(data: String): Unit = {
println("feature selection")
super.model(data)}
}
trait FEN extends ML{
override def model(data: String): Unit = {
println("feature engineering")
super.model(data)}
}
trait DP extends ML{
override def model(data: String): Unit = {
println("data preprocessing")
super.model(data)}
}
class p extends FE with FEN with DP {
def pipeline(data: String)={
println(data)
println("start pipeline")
super.model(data)
}
}
def main(args: Array[String]): Unit = {
val test0 = new p
test0.pipeline("input data")
}
}
// pipeline方法执行结果如下
input data
start pipeline
data preprocessing
feature engineering
feature selection
选择模型及超参数
finish model
特质继承类
特质可以继承类,而这个类会成为所有混入该特质的超类。
因为在 Scala 中,一个类只能有一个超类,所以以如下代码为例说明注意事项
class a{}
trait b extends a{}
// 合法 因为c扩展的类是特质b超类的一个子类
class c extends a with b{}
class d
// 不合法 因为e扩展的类不是特质b超类的子类
class e extends d with b{}
当特质中有多个抽象方法,而我们只需要用到其中的一个或几个方法的时候,直接去“继承”该特质明显是不明智的选择,因为那意味着我们需要重写该特质内的所有抽象方法。这时候我们去定义一个抽象类“继承”该特质,重写特质中的抽象方法,使方法体为空,然后再定义我们需要的类去继承该抽象类比较好。
trait A{
// 抽象方法1
// 抽象方法2
// 抽象方法3
}
abstract class B extends A{
// 重写抽象方法A 方法体为空
// 重写抽象方法B 方法体为空
// 重写抽象方法C 方法体为空
}
class C extends B{
// 需要使用哪个方法就重写哪个方法
}
参考资料:
https://www.bilibili.com/video/BV1mt4y117uk?p=91
《快学Scala》
《Scala编程》