本文介绍Scala中的trait特质。
1、基本知识
特质 (Traits) 用于在类 (Class)之间共享程序接口 (Interface)和字段 (Fields)。 它们类似于Java 8的接口。
即:特质里写了一些方法 和 字段,而这些是可以被所有类所共用、共享的。
这就像在类之间搭建了桥梁一样(或者打上了相同的标记),使得所有用了这个特质的类都可以有相同的特质中的方法和字段。
当然一个类可以使用多个特质,而不仅限于一个特质。
类和对象 (Objects)可以扩展特质,但是特质不能被实例化,因此特质没有参数。
特质使用关键词trait定义。
某种程度上看特质trait和类class很像,它们都不能独立运行,类必须被实例化,而特质必须被extends扩展(或继承)。
但特质比类更方便作为共享接口的地方就在于,它可以定义没有任何方法、只是约定了输入/输出参数类型的函数。
而这就可以作为类型/模式匹配的模版使用。
2、trait的定义方法
2.1 定义没有执行方法的trait
//in Scala
trait Cardetails{
def details(d:String):String
}
2.2 定义有执行方法的trait
//in scala
trait detcar{
def readdetails(d:String):String =
Source.fromString(d).mkString
}
3、trait的使用方法
3.1 类使用extends来继承没有定义任何执行方法的特质
//in scala
//定义一个特质
trait Cardetails{
def details(d:String):String
}
//定义一个类集成该特质,并在类中重写特质中的方法
class Cardet extends Cardetails {
import scala.io.Source
override def details(source:String) = {
Source.fromString(source).mkString
}
}
object car {
def main(args:Array[String]){
val c1 = new Cardet //实例化类
println(c1.details("Car details are being displayed")) //调用类中的方法
println(c1.isInstanceOf[Cardetails]) //验证下类是不是继承了这个特质
}
}
3.2 一个类即继承了一个类,同时又继承了一个特质,使用extends (类) with(特质)语法
//in scala
import scala.io.Source
//定义一个特质 detcar
trait detcar{
def readdetails(d:String):String =
Source.fromString(d).mkString +": this from trait"
}
//定义一个有参数的类Car
class Car(var cname:String, var cno:Int){
def details = cname+" "+cno
}
//定义一个有参数的类Alto,该类继承了类Car,同时拥有特质detcar。
class Alto( cname:String, cno:Int,var color:String) extends Car(cname,cno) with detcar{
//子类重写了父类的方法details,并且在重写的方法中引用了特质的方法
override def details = {
val det = readdetails(color)
cname+"\n"+cno+"\n"+"Color:"+color +"\n" +det
}
}
object Student {
def main(args:Array[String]){
val a1 = new Alto("Alto",34,"Black")
println(a1.details)
}
}
3.3 特质trait的多继承
特质可以继承特质,类也可以继承多个特质。
object TraitOrder extends App {
trait Logger {
println("Logger")
}
//一个trait 继承另一个trait
trait FileLogger extends Logger {
println("FileLogger")
}
trait Closable {
println("Closable")
}
class Person{
println("Constructing Person...")
}
//一个类继承了父类,又继承了多个特质trait
class Student extends Person with FileLogger with Closable {
println("Constructing Student ...")
}
new Student
}
3.4 多个特质的继承顺序
准则:
- 如果有超类,则先调用超类的函数。
- 如果混入的trait有父trait,它会按照继承层次先调用父trait的构造函数。
- 如果有多个父trait,则按顺序从左到右执行。
- 所有父类构造函数和父trait被构造完之后,才会构造本类的构造函数。
这个准则依次对应到3.3中的代码为:
1、Student有父类Person
2、特质FileLogger有父特质 Logger
3、Student同时继承了两个父特质FileLogger 、Closable,继承顺序是从左到右执行
4、父类Person、特质Logger\FileLogger\Closable构造完后,Student类的构造函数才会开始
4、比较和选择使用trait 、abstract class的场景
在有可重复使用的方法、收集器的场景中,我们该怎么判断是使用trait 还是 abstract class呢?
以下4条基线可以参考:
- 如果运行效率是你关注的一个指标,那么建议使用抽象类。 因为Traits 被编译为接口,可能会有轻微的性能开销。
- 如果你需要继承一些JAVA代码,那么最好使用抽象类,因为特征没有 Java 模拟(analog),从特征继承就会很奇怪。
- 如果行为不可重用,建议使用具体类。
- 如果行为在多个不相关的类中重用,则可以使用特征。