前言
前文講到,Kotlin中萬物皆對象,連《基礎數值類型》都不放過。
除了文字版本,也有Xmind版本 github地址
帶幾個問題吧
① Any 跟object什麼區別?
② 子類的初始化順序如何?
③ super的覆蓋規則是什麼?
④ 與java對比,kotlin在繼承有什麼變化?加強了什麼?
本文從以下幾個方面講繼承
- 根是Any類
- 構造函數
- 重寫方法
- 重寫屬性
- 中場小結
- 派生類初始化順序
- 調用超類實現
- super覆蓋規則
根是Any類
首先,Any 不是 java.lang.Object。
在Kotlin中,Any是所有類的超類
如果沒有顯式聲明超類型聲明的類,其默認的超類是Any
Any 默認提供了三個函數
- equals()
- hashCode()
- toString()
把超類型 放在冒號 之後
open class Base(p: Int)
class Derived(p: Int) : Base(p)
構造函數
子類有主構造函數
如果子類有主構造函數, 則基類必須在主構造函數中立即初始化。
這話看起來看起來,比較難懂。
先看個範例
open class Person(var name : String, var age : Int, var score : Int){// 基類
}
class Student(name : String, age : Int, var no : String, score : Int) : Person(name, age, score) {
}
// 測試
fun main(args: Array<String>) {
val s = Student("wangxueming", 18, "S12346", 89)
println("學生名: ${s.name}")
println("年齡: ${s.age}")
println("學生號: ${s.no}")
println("成績: ${s.score}")
}
講解
Person是基類。Student是子類。
Person定義了name, age, score。Student有參數:name、age、no、score。
如果子類有主構造函數, 則基類必須在主構造函數中立即初始化。
就是說:如果基類Person定義的name、age、score,在子類中必須要進行賦值。
即 : 冒號之後,Person(name, age, score) 必須要把參數傳入。
如果在name上加上var呢?
class Student(var name : String, age : Int, var no : String, score : Int) : Person(name, age, score) {
}
輸出結果
‘name’ hides member of supertype ‘Person’ and needs ‘override’ modifier
報錯了,因爲Person的name與Student的name重名了;
換言之。如果你把Person的name,換個名字,改成otherName。
或者把Person的name改成otherName結果怎麼樣呢?
會錯嗎?答案是不會的。
open class Person(var otherName : String, var age : Int, var score : Int){// 基類
}
class Student(var myName : String, age : Int, var no : String, score : Int) : Person(myName, age, score) {
}
// 測試
fun main(args: Array<String>) {
val s = Student("wangxueming", 18, "S12346", 89)
println("學生名: ${s.otherName}")
println("年齡: ${s.age}")
println("學生號: ${s.no}")
println("成績: ${s.score}")
}
這裏會正常輸出結果
學生名: wangxueming
年齡: 18
學生號: S12346
成績: 89
子類沒有主構造函數
必須在每一個二級構造函數中用 super 關鍵字初始化基類,或者在代理另一個構造函數
看範例
/**用戶基類**/
open class Person(name:String){
/**次級構造函數**/
constructor(name:String,age:Int):this(name){
//初始化
println("-------基類次級構造函數---------")
}
}
/**子類繼承 Person 類**/
class Student:Person {
/**次級構造函數**/
constructor(name:String,age:Int,no:String,score:Int):this(name,age,no,score, 0){
println("-------繼承類次級構造函數 A---------")
}
/**次級構造函數**/
constructor(name:String,age:Int,no:String,score:Int, sex : Int):super(name,age){
println("-------繼承類次級構造函數 B---------")
println("學生名: ${name}")
println("年齡: ${age}")
println("學生號: ${no}")
println("成績: ${score}")
println("性別: ${sex}")
}
}
fun main(args: Array<String>) {
var s = Student("wangxueming", 18, "S12345", 89)
println()
println()
println()
var s2 = Student("wangxueming", 18, "S12345", 89, 1)
}
輸出結果
-------基類次級構造函數---------
-------繼承類次級構造函數 B---------
學生名: wangxueming
年齡: 18
學生號: S12345
成績: 89
性別: 0
-------繼承類次級構造函數 A----------------基類次級構造函數---------
-------繼承類次級構造函數 B---------
學生名: wangxueming
年齡: 18
學生號: S12345
成績: 89
性別: 1
這個結果。
Student(“wangxueming”, 18, “S12345”, 89)
用this在代理另一個構造函數是
Student(“wangxueming”, 18, “S12345”, 89, 1)
用super 關鍵字初始化基類
重寫方法
Kotlin 力求清晰顯式
加強了繼承的限制。也就加強了 類之間的關聯界定。
有幾種情況要注意:
- 使用fun聲明函數時,此函數默認爲final修飾,不能被子類重寫
- 如果允許子類重寫該函數,那麼就要手動添加 open 修飾它
- 子類重寫方法使用 override 關鍵詞
跟java的差異
- 能否被繼承需要open關鍵字標明
- 默認無法被繼承
重寫屬性
用override關鍵字。
這基本跟java一致。
在Kotlin中,override某變量,必須要標註override!
這也是加強了類繼承中,變量重寫的界定。
中場小結
kotlin較java
kotlin對繼承更細化,對繼承中,增強 改變的部分 的標註
派生類初始化順序
先說結論
1、父結構體
2、父結構體內參數處理
3、子類 init & 屬性
4、父類init & 屬性
看範例
open class Base(val name: String) {
open val size: Int =
name.length.also { println("Initializing size in Base: $it") }
init { println("Initializing Base"+size) }
}
class Derived(
name: String,
val lastName: String
) : Base(name.capitalize().also { println("Argument for Base: $it") }) {
init { println("Initializing Derived") }
override val size: Int =
(super.size + lastName.length).also { println("Initializing size in Derived: $it") }
}
fun main() {
println("Constructing Derived(\"hello\", \"world\")")
val d = Derived("hello", "world")
}
結果輸出
Constructing Derived("hello", "world")
Argument for Base: Hello
Initializing size in Base: 5
Initializing Base0
Initializing Derived
Initializing size in Derived: 10
注意
- 初始化是有順序的
- 留意:打印的屬性值
- 看Base中的size值,在init中是無法正確獲取的
- 基類的open屬性,不能被構造函數、屬性初始化器、init塊中使用open成員
調用超類實現
使用 super 關鍵字
基本與java一致
留意
在一個內部類中訪問外部類的超類,可以通過由外部類名限定的 super 關鍵字來實現:super@Outer
我們來看個範例,瞭解下
class FilledRectangle: Rectangle() {
fun draw() { /* …… */ }
val borderColor: String get() = "black"
inner class Filler {
fun fill() { /* …… */ }
fun drawAndFill() {
[email protected]() // 調用 Rectangle 的 draw() 實現
fill()
println("Drawn a filled rectangle with color ${[email protected]}") // 使用 Rectangle 所實現的 borderColor 的 get()
}
}
}
super覆蓋規則
一個類繼承多個
爲了表示採用從哪個超類型繼承的實現,需要顯式的標明。
用由尖括號中超類型名限定的 super進行標明,如 super
範例
open class Rectangle {
open fun draw() { /* …… */ }
}
interface Polygon {
fun draw() { /* …… */ } // 接口成員默認就是“open”的
}
class Square() : Rectangle(), Polygon {
// 編譯器要求覆蓋 draw():
override fun draw() {
super<Rectangle>.draw() // 調用 Rectangle.draw()
super<Polygon>.draw() // 調用 Polygon.draw()
}
}