2015/10/5

Early Initializer in Scala

在 scala 的繼承關係中,我們有時候需要在子類別中,修改父類別中定義的變數初始值,而不是使用原本父類別初始的數值,但如果用一般的 override 語法,會發現得不到預期的結果,這時候,可以使用 early initializer 語法。

override val

這是一段很簡單的繼承的範例,當我們在 REPL 測試下列的程式時,可以發現有個變數 bar 得到的結果,跟預期的結果不同。

trait A {
    val foo: Int
    def bar: Int = 10
    println("In A: foo: " + foo + ", bar: " + bar)
}
class B extends A {
    val foo: Int = 25
    println("In B: foo: " + foo + ", bar: " + bar)
}
class C extends B {
    override val bar: Int = 99
    println("In C: foo: " + foo + ", bar: " + bar)
}

scala> new C
In A: foo: 0, bar: 0
In B: foo: 25, bar: 0
In C: foo: 25, bar: 99

val initialization rules

scala 在處理 val 變數的 initialization 以及 override,會遵循以下的規則,也因為這些條件,我們在 B 以及 A 裡面使用 bar 的時候,會得到 0 這個值。

  1. 父類別會先於子類別進行初始化
  2. 類別中定義的 members,是依照宣告的順序進行初始化
  3. 當 val 被 overridden 時,這個變數只能夠被初始化一次:這裡的意思是,我們不能在class B 中,再一次 def bar: Int = 20,compiler 會拒絕這樣的寫法,只能加上 override 覆寫 bar。
  4. 被 overridden 的 val 變數,將會在初始化父類別時,設定為預設的初始值,以 Int 來說就是 0

Eraly Initializer

如果我們希望在子類別中,賦予父類別定義的變數,一個不同於 0 的初始值,我們可以使用 Early Initialier 的語法,如同下面程式碼中的類別 C。

trait A {
    val foo: Int
    val bar = 10
    println("In A: foo: " + foo + ", bar: " + bar)
}
class B extends A {
    val foo: Int = 25
    println("In B: foo: " + foo + ", bar: " + bar)
}
class C extends {
    override val bar = 99
} with B {
    println("In C: foo: " + foo + ", bar: " + bar)
}

scala> new C
In A: foo: 0, bar: 99
In B: foo: 25, bar: 99
In C: foo: 25, bar: 99

這時候,override val bar 就會在初始化父類別之前,先初始化 bar 這個變數,因此在父類別 B 與 A 裡面,就可以使用到 bar 的初始值 99。

References

Scala Puzzlers: Now You See Me, Now You Don't

Scala: Example use for early definition / early initializer / pre-initialized fields

In Scala, what is an 「early initializer」?

Syntax for Early Definitions