Trait
可以將 Trait 作爲接口來使用,此時的 Triat 就與 Java 中的接口非常類似。
在 Triat 中可以定義抽象方法,就與抽象類中的抽象方法一樣,只要不給出方法的具體實現即可。
類可以使用 extends 關鍵字繼承 trait,注意並不是 implement,而是 extends,在 Scala 中沒有 implement 的概念,無論繼承類還是 trait,統一都是 extends 。
類繼承 trait 後,必須實現其中的抽象方法,實現時不需要使用 override 關鍵字。
Scala 不支持對類進行多繼承,但是支持多重繼承 trait,使用 with 關鍵字即可。
trait HelloTrait{
def sayHello(name:String)
}
trait MakeFriendsTrait{
def makeFriends(p:Person)
}
class Person(val name:String) extends HelloTrait with MakeFriendsTrait with Cloneable with Serializable{
def sayHello(otherName:String) = println("Hello," + otherName + ",I'm " + name)
def makeFriends(p:Person) = println("Hello,my name is " + name + ",your name is " + p.name)
}
// 創建實例進行測試
val p = new Person("Li")
val q = new Person("Chy")
p.sayHello("Tom")
// 輸出爲: Hello,Tom,I'm Li
p.makeFriends(q)
// 輸出爲: Hello,my name is Li,your name is Chy
在Trait中定義具體方法
Scala 中的 Triat 可以不是隻定義抽象方法,還可以定義具體方法,此時 trait 更像是包含了通用工具方法的東西,稱爲 triait 的功能混入的了類。
舉例來看就是, trait 中可以包含一些很多類都通用的功能方法,比如打印日誌等,spark 中就使用了 trait 來定義了通用的日誌打印方法。
// example 1
// 從這裏可以看到在 trait 裏面是可以定義具體的方法的
trait Logger{
def log(msg:String) = println("log: " + msg )
}
// 可以直接調用繼承的 Logger 裏面的方法
class Person(val name:String) extends Logger{
def sayHello{
println("Hello,I'm " + name);
log("sayHello is invoked")
}
}
// 測試
val p = new Person("Li")
p.sayHello
//測試輸出
// Hello,I'm LI
// log: sayHello is invoked
在Trait中定義具體字段
Scala 中的 Triat 可以定義具體 field,此時繼承 trait 的類就自動獲得了 trait 中定義的 field 。
但是這種獲取 field 的方式與繼承 class 是不同的:如果是繼承 class 獲取的 field,實際是定義在父類中的;而繼承 trait 獲取的 field,就直接被添加到了類中。
所以看起來雖然一樣,但是底層的原理是不一樣的。
trait Person {
val eyeNum: Int = 2
}
class Student(val name: String) extends Person {
def sayHello = println("Hi, I'm " + name + ", I have " + eyeNum + " eyes.")
}
在Trait中定義抽象字段
Scala 中的 Triat 可以定義抽象 field,而 trait 中的具體方法則可以基於抽象 field 來編寫。
但是繼承 trait 的類,則必須覆蓋抽象 field,提供具體的值。
trait SayHello{
val msg:String
def sayHello(name:String) = println(msg + ", " + name)
}
class Person(val name:String)extends SayHello{
val msg:String = "hello"
def makeFriends(p:Person){
sayHello(p.name)
println("I'm" + name + ",I want to make friends with you!")
}
}
// 測試
val p1 = new Person("Li")
val p2 = new Person("Chy")
p1.makeFriends(p2)
爲實例混入trait
有時我們可以在創建類的對象時,指定該對象混入某個 trait,這樣,就只有這個對象混入該 trait 的方法,而類的其他對象則沒有。
trait Logged{
def log(msg:String){}
}
trait MyLogger extends Logged{
override def log(msg:String){
println("log:" + msg)
}
}
class Person(val name:String) extends Logged {
def sayHello{
println("Hi,I'm " + name);
log("sayHello is invoked!")
}
}
val p1 = new Person("Li")
p1.sayHello
// p1.sayHello 的輸出爲:Hi,I'm Li
// 可見 log("sayHello is invoked!") 並沒有輸出,這裏還是調用的 Logged 裏面的方法,什麼都沒有做。
val p2 = new Person("Li") with MyLogger
// 通過 with 動態混入到具體的對象中,因爲混入的 trait 中覆蓋了 Logged 中的方法,所以執行 MyLogger 中的 log 方法。
p2.sayHello
// p2.sayHello 的輸出爲:
// Hi,I'm Li
// log:sayHello is invoked!
trait調用鏈
Scala 中支持讓類繼承多個 trait 後,依次調用多個 trait 中的同一個方法,只要讓多個 trait 的同一個方法中,在最後都執行 super. 方法即可。
類中調用多個 trait 中都有的這個方法時,首先會從最右邊的 trait 的方法開始執行,然後依次往左執行,形成一個調用鏈條。
這種特性非常強大,其實就相當於設計模式中的責任鏈模式的一種具體實現依賴。
trait Handler{
def handle(data:String){}
}
trait DataValidHandler extends Handler{
override def handle(data:String){
println("check data: " + data)
super.handle(data)
}
}
trait SignatureValidHandler extends Handler{
override def handle(data:String){
println("check signature: " + data)
super.handle(data)
}
}
class Person(val name:String) extends SignatureValidHandler with DataValidHandler{
def sayHello = {
println("Hello, " + name )
handle(name)
}
}
// 測試
val p = new Person("Li")
p.sayHello
// 結果
// Hello, Li
// check data: Li
// check signature: Li
在trait中覆蓋抽象方法
在 trait 中,是可以覆蓋父 trait 的抽象方法的。
此時如果要通過編譯,就得給子 trait 的方法加上 abstract override 修飾。
trait Logger{
def log(msg:String)
}
trait MyLogger extends Logger{
abstract override def log(msg:String) {
super.log(msg)
}
}
混合使用trait的具體方法和抽象方法
在 trait 中,可以混合使用具體方法和抽象方法。
可以讓具體方法依賴於抽象方法,而抽象方法則放到繼承 trait 的類中去實現。
這種 trait 其實就是設計模式中的模板設計模式的體現。
// 在這裏的體現就是 getName 並沒有具體實現,後續的方法可以直接使用。
trait Valid{
def getName:String
def valid:Boolean = {
getName == "Li"
}
}
class Person(val name:String) extends Valid{
println(valid)
def getName = name
}
// 測試
val p1 = new Person("Li")
val p2 = new Person("chy")
// p1 的返回會有 true
// p2 的返回會有 false
trait的構造機制
在 Scala 中,trait 也是有構造代碼的,也就是 trait 中的,不包含在任何方法中的代碼。
而繼承了 trait 的類的構造機制如下:
- 父類的構造函數執行;
- trait 的構造代碼執行,多個 trait 從左到右依次執行;
- 構造 trait 時會先構造父 trait,如果多個 trait 繼承同一個父 trait,則父 trait 只會構造一次;
- 所有 trait 構造完畢之後,子類的構造函數執行。
class Person{
println("Person's constructor!")
}
trait Logger{
println("Logger's constructor!")
}
trait MyLogger extends Logger{
println("MyLogger's constructor!")
}
trait TimeLogger extends Logger{
println("TimeLogger's constructor!")
}
class Student extends Person with MyLogger with TimeLogger{
println("Student's constructor!")
}
// 測試
val s = new Student
測試結果如下:
//這是在命令提示符界面的輸出結果。
scala> val p = new Student
Person's constructor!
Logger's constructor!
MyLogger's constructor!
TimeLogger's constructor!
Student's constructor!
p: Student = Student@61911947
從以上可以看出,當創建時,先是構造父類,再構造 trait ,從左到右執行 constructor,對於 MyLogger 和 TimeLogger 來說,先構造了 Logger ,雖然這兩個都繼承了 Logger ,但是輸出只有一行,也就是隻會構造一次,然後是 Student 自己的 constructor 。
trait field的初始化
在 Scala 中,trait 是沒有接收參數的構造函數的,這是 trait 與 class 的唯一區別,但是如果需求就是要 trait 能夠對 field 進行初始化, 使用 Scala 中非常特殊的一種高級特性——提前定義 。
trait繼承class
在 Scala 中,trait 也可以繼承自 class ,此時這個 class 就會成爲所有繼承該 trait 的類的父類。
class MyUtil{
def printMessage(msg:String) = println(msg)
}
trait Logger extends MyUtil{
def log(msg:String) = printMessage("log: " + msg)
}
class Person(val name:String) extends Logger{
def sayHello{
log("Hi,I'm " + name)
printMessage("Hi, I'm " + name)
}
}