Kotlin入門-父子身份更分明,繼承篇

在這裏插入圖片描述

前言

前文講到,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()
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章