類別繼承
繼承是什麼? 為什麼要有繼承?
假設我們有二個類別,分別為 class Pikachu(皮卡丘) 及 class Eve(依布),而這二個類別都同樣有 level的物件變數,且這二個類別都是神奇寶貝 “Pokemon”。
那麼我們就可以把共通的屬性 level置於父類別Pokemon,然後Pikachu及Eve繼承Pokemon。這樣就可以直接copy Pokemon 的 level屬性。
fun main(args:Array<String>){
var p1=Pikachu()
p1.level=100
var e1=Eve()
e1.level=10
println("p1 level : ${p1.level}")
println("e1 level : ${e1.level}")
}
open class Pokemon{
var level:Int=0
constructor(){
println("神奇寶貝出生了")
}
}
class Pikachu: Pokemon{
constructor(){
println("皮卡丘出生了")
}
}
class Eve: Pokemon{
constructor(){
println("Eve出生了")
}
}
結果:
神奇寶貝出生了
皮卡丘出生了
神奇寶貝出生了
Eve出生了
p1 level : 100
e1 level : 10
Pikachu及Eve二個子類別,並沒有宣告level這個物件變數,但卻可以在p1及e1取得level,這就表示Pikachu及Eve這二個類別 copy了Pokemon裏的level變數。
class 預設為final
kotlin的類別預設為final,是不准被繼承的。所以為了要能繼承Pokemon, 就必需父類別Pokemon之前加上open 關鍵字,才可被Pikachu繼承。
繼承運算子 “:”
kotlin繼承使用 “:” 運算子,跟Java的 “extends” 是同樣的意思。
class Pikachu : Pokemon{}
Any類別
類別若沒有繼承的父類別,則系統會自動加入繼承Any類別。也就是說,Any是所有類別的父類別。只有Any沒有老爸,他是從石頭蹦出來的。
class Pokemon:Any{//系統自動加入Any
}
建構子
最簡單的繼承如下,子類別一定要有建構子。如此才有機會在後面加入 :super() 調用父類別建構子
fun main(args:Array<String>){
var p1=Pikachu()
}
open class Pokemon{}
class Pikachu:Pokemon{
constructor(){//在此系統會自動加入:super()
}
}
除了使用次建構子外,也可以在繼承中指定父類別建構子的進入點
open class Pokemon{}
class Pikachu:Pokemon(){}
如果子類別有主建構子,那繼承時就必需手動指定要調用的父類別建構子
open class Pokemon(a:Int){}
class Pikachu(var level:Int):Pokemon(10){}
如果子類別有主建構子及次建構子,那次建構子一定要手動加入 :this()調用主建構子,順序如下
1. 次建構子藉由 :this() 調用主建構子
2. 主建構子調用父類別建構子
open class Pokemon(a:Int){}
class Pikachu(var level:Int):Pokemon(10){
constructor():this(1){}
}
建構子覆蓋
子類別建構子覆蓋父類別建構子時,父類別建構子不需要啟用 “open” 關鍵字。因為建構子預設就是open,而且也不容許手動設為final。
但請注意,建構子是可以設定為private的,如 private constructor():this(1{}
open class Pokemon(a:Int){
constructor():this(1){
println("神奇寶貝出了")
}
}
class Pikachu(var level:Int):Pokemon(){
constructor():this(1){
println("皮卡丘出生了")
}
}
結果:
神奇寶貝出了
皮卡丘出生了
物件變數覆蓋(override)
如果父子類別的物件變數同名,則子類別物件變數就會覆蓋父類別物件變數。不過父類別物件變數預設為final,是不可被覆蓋的。所以父類別的物件變數必需加上open,子類別物件變數必需加上override。如下所示
fun main(args:Array<String>){
var p1=Pikachu(10)
}
open class Pokemon (open var level:Int){
constructor():this(10){
println("父類別建構子")
}
}
class Pikachu(override var level:Int): Pokemon(level){
}
方法覆蓋
子類別覆蓋父類別方法時,需注意父類別方法預設是final的,所以需在父類別方法前加上open,然後於子類別方法前再加入 override。
複習一下,方法預設是final,但建構子預設是open,而且不能更改成final。
fun main(args:Array<String>){
var p1=Pikachu()
p1.eat()
}
open class Pokemon{
var level:Int=0
open fun eat(){
println("素食的")
}
}
class Pikachu: Pokemon(){
override fun eat(){
println("雜食的")
}
}
結果:
雜食的
多型
子類別物件可以使用父類別參考,如下產生一個p2 的Pikachu,但他的型態卻是Pokemon。p2此時就無法使用子類別的擴充功能(lightning),但可借由 p2 as Pikachu 進行強制轉型而取出lightning的功能。
不過e1是Eve物件,也是使用Pokemon參考,此時e1也可由 e1 as Pikachu 強制轉型成Pikachu。這種現像稱為 “爛梨假蘋果”。經由偽裝後的e1可以欺騙編譯器取得 lightning的擴充功能。不過在執行
(e1 as Pikachu).lightning() ,會產生runtime error。所以在轉型前,最後好 if (e1 is Pikachu)先判斷一次再轉型。
fun main(args:Array<String>){
var p1=Pikachu()
p1.lightning()
var p2:Pokemon=Pikachu()
(p2 as Pikachu).lightning()
var e1:Pokemon=Eve()
if(e1 is Pikachu)
(e1 as Pikachu).lightning()
}
open class Pokemon{
var level:Int=0
}
class Pikachu: Pokemon(){
fun lightning(){
println("十萬伏特閃電攻擊")
}
}
class Eve: Pokemon(){}