Scala繼承與Java的區別
阿新 • • 發佈:2019-02-07
在之前的筆記Java靜態屬性和方法的繼承問題中,通過具體的實驗證明,在子類中重寫父類的欄位時並沒有覆蓋父類的欄位,只是隱藏了父類的欄位。而在scala中則不同,scala子類的同名欄位會重寫且覆蓋父類的同名欄位,這裡做了個簡單實驗,並記錄下來。
Parent.scala
class Parent {
val normalStr: String = "Normal member of parent."
def normalMethod() = {
println("Normal method of parent.")
}
}
定義了一個欄位normalStr
和一個方法normalMethod()
Child.scala
class Child extends Parent {
override val normalStr: String = "Normal member of child."
override def normalMethod() = {
println("Normal method of child.")
}
}
子類Child
繼承了父類Parent
,並override
父類的normalStr
和normalMethod()
。
TestMain和Result
object TestMain{
def main(args: Array[String]) {
val child: Child = new Child
println(child.normalStr)
child.normalMethod()
//val child1:Parent = child.asInstanceOf[Parent]
//採用Parent型別的變數指向建立的Child物件
val child1:Parent = new Child
println(child1.normalStr)
child1.normalMethod()
}
}
輸出的結果如下:
Normal member of child.
Normal method of child.
Normal member of child.
Normal method of child.
從結果可以看出,子類重寫並覆蓋了父類的同名屬性和方法
Scala子類的構造順序
這裡順便記錄下Scala子類的構造順序,這裡直接用書上給出的例子,以便後續檢視:
先寫兩個類,一個父類Creature.scala
,一個子類Ant.scala
:
Creature
class Creature {
val range: Int = 10
val env: Array[Int] = new Array[Int](range)
def show(): Unit = {
println(range)
}
}
Ant
class Ant extends Creature {
override val range = 2
}
現在建立一個Ant
的物件ant
,那麼ant.env.length
的值是多少,憑第一感覺應該是10或者2,然而答案是0,接下來我寫下ant
建立的過程中構造器的執行順序:
- 首先呼叫父類
Creature
的構造器(父類的構造器先於子類的構造器被呼叫),所以首先把range
設定為10。為了後續的說明這裡說明下,類的欄位是由一個私有屬性和對應的getter
和setter
方法組成的,而子類在重寫父類的同名欄位時,對於val
型別的屬性子類重寫了getter
方法。 - 接下來初始化
env
陣列,所以需要呼叫range
的getter
方法,然而子類已經重寫了getter
方法,且子類並沒初始化,所有的欄位都是物件建立過程中,記憶體清零後的預設值,所以此時range
的值為0。這也就解釋了上述問題的疑問。 - 接下來呼叫子類的構造器,
range
被設為2。
所以在構造器中,物件的初始化不應該依賴於val
的值,因為val
的值對應的getter
方法可能會被子類重寫覆蓋。解決辦法有:
- 將
val
宣告為final。(簡單高效,但是不夠靈活) - 在超類中將
val
宣告為lazy
。(簡單靈活,但是不夠高效) - 還有種就是子類中使用提前定義語法。(這個就不介紹了)
ant
物件呼叫show()
方法輸出的則是子類range
的值,即為2
。而在Java
中,則是父類的range
的值:10
。主要原因還是由於在Scala
中,子類重寫父類的屬性或者方法,覆蓋了父類的屬性和方法,而在Java
中,只有非靜態的方法會被子類重寫覆蓋,而非靜態/靜態屬性和靜態方法都只是被隱藏了。
主要參考:《快學Scala》