在 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 這個值。
- 父類別會先於子類別進行初始化
- 類別中定義的 members,是依照宣告的順序進行初始化
- 當 val 被 overridden 時,這個變數只能夠被初始化一次:這裡的意思是,我們不能在class B 中,再一次 def bar: Int = 20,compiler 會拒絕這樣的寫法,只能加上 override 覆寫 bar。
- 被 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
沒有留言:
張貼留言