Scala 【 9 Trait 】

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)
    }
}

發佈了357 篇原創文章 · 獲贊 58 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章