本文介紹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),從特征繼承就會很奇怪。
- 如果行為不可重用,建議使用具體類。
- 如果行為在多個不相關的類中重用,則可以使用特征。