【Scala谜题】继承
多級繼承
Scala 支持面向對象的編程概念,繼承是它的一個很重要的特征。繼承通常對父類和特質中定義的缺省值的重載很有用。當增加多級繼承時事情變得更加有趣,例如下面這段程序。
trait A {val foo: Intval bar = 10println("In A: foo: " + foo + ", bar: " + bar) } class B extends A {val foo: Int = 25println("In B: foo: " + foo + ", bar: " + bar) } class C extends B {override val bar = 99println("In C: foo: " + foo + ", bar: " + bar) } new C()它的打印結果我一開始也沒想到:
In A:foo:0,bar:0 In B:foo:25,bar:0 In C:foo:25,bar:99首先我們要知道 val 的初始化和重載規則:
因此,根據以上規則,在最開始的那段程序中,雖然表面上看在特質 A 中給 bar 分配了一個初始值,實際上卻不是這樣,因為類 C 中重載了 bar。這意味著特質 A 構造時,給bar 分配了一個缺省初始值
0,而不是原有的值 10。
Scala 從Java 中繼承了初始化順序規則。Java 確保首先初始化超類,這樣就可以從子類構造器中安全使用超類字段,確保正確地初始化字段。特質會被編譯成接口和具體的(非抽象的)類,所以也可應用同樣的規則。
Scala 給記錄賦的缺省初始值為:
- Byte、Short 和Int 類型val 的初始值是0
- Int、Long、Float 和Double 類型val 的初始值分別是0L、0.0f 和0.0d
- Char 類型val 的初始值是'0'
- Boolean 類型val 的初始值是false
- Unit 的初始值是()
- 所有其他類型的初始值是 null
那如果我們想要展示的是這樣的結果:
In A:foo:0,bar:99 In B:foo:25,bar:99 In C:foo:25,bar:99我們應該怎么做呢:
用定義
一種方法是將 bar 聲明為 def,而不是 val。之所以這個方法能解決問題,是因為 def 這個方法體不屬于主構造器,因此不參與類初始化。此外,因為類 C 中重載了 bar,多態會特別選擇使用這個重載的定義。因此,3 個 println 語句中的 bar 都會調用類 C 中的重載定義。
trait A {val foo: Intdef bar: Int = 10println("In A: foo: " + foo + ", bar: " + bar) } class B extends A {val foo: Int = 25println("In B: foo: " + foo + ", bar: " + bar) } class C extends B {override def bar: Int = 99println("In C: foo: " + foo + ", bar: " + bar) }這種方法的一個缺點是每次調用都要評估。Scala 也遵從統一訪問原則,所以在超類中定義一個參數方法不會阻止在子類中將它重載為一個 val,這會導致令人迷惑的行為再次出現,從而破壞原有的架構規劃。
lazy val
另外一種避免這種意外的方法是將 bar 聲明為 lazy val。lazy val 在初次訪問時初始化。而常規的 val,又叫靜態變量,是在定義時初始化的。lazy val 使用編譯器生成的方式初始化,這里將調用特質 C 中 bar 的重載版本。注意,lazy val 的特點是將高成本的初始化過程盡可能推遲到最后時刻(有時可能永遠也不進行初始化)。
trait A {val foo: Intlazy val bar = 10println("In A: foo: " + foo + ", bar: " + bar) } class B extends A {val foo: Int = 25println("In B: foo: " + foo + ", bar: " + bar) } class C extends B {override lazy val bar = 99println("In C: foo: " + foo + ", bar: " + bar) }不過,要注意的是,lazy val 也有一些缺點:
預初始化字段
使用預初始化字段(也就是大家所知道的早期初始化器)也可以達到相同的效果:
trait A {val foo: Intval bar = 10println("In A: foo: " + foo + ", bar: " + bar) } class B extends A {val foo: Int = 25println("In B: foo: " + foo + ", bar: " + bar) } class C extends {override val bar = 99 } with B {println("In C: foo: " + foo + ", bar: " + bar) }這段程序與原來的程序的唯一差別,就是 bar 在類 C 的早期字段定義從句中初始化。早期字段定義從句緊跟著 extends 關鍵字后的大括號,它是子類的一部分,在超類構造器之前運行。這樣就可以確保 bar 在特質 A 被構造之前即被初始化。
總結
用什么方法解決潛在的初始化順序問題,是因不同的用例而有所不同的:
- 如果每次訪問評估表達式的成本不是太高,也許會用定義的方法。
- 或者只要能避免循環依賴,就可以用 lazy val 的方法,這對用戶的類來說也許是最簡單的解決方案。
- 或者,如果用戶很清楚他們應該使用早期字段定義,那么簡單地使用原來的抽象 val 也是一個不錯的選擇。
總結
以上是生活随笔為你收集整理的【Scala谜题】继承的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python tkinter chk
- 下一篇: visitor-访问模式