Kotlin——中級篇(四):繼承類詳解

Jetictors發表於2019-03-04

Kotlin——中級篇(四):繼承類詳解

在前面的章節中,詳細的詳解了的使用,但是由於篇幅的限制,關於類的很多特性都沒有講解到。今天在這篇文章中,詳細的講解Kotlin中類的特性。如果您對Kotlin中的類還沒有一個整體的瞭解的話,請參見我上一篇文章Kotlin——中級篇(一):類(class)詳解
眾所周知,Kotlin是一門物件導向的開發語言。那麼他也有物件導向語言的特性。而物件導向的三大特性即封裝繼承多型。這是每一門物件導向語言否具有的特性。今天這一節會著重的講解Kotlin的繼承Java的不同處和Kotlin獨有的特點。

目錄

Kotlin——中級篇(四):繼承類詳解

一、物件導向的特徵

物件導向的三大特徵:封裝繼承多型

由於物件導向的三大特徵太過於普通,而且這並不是Kotlin中特有的知識。在這裡就不多做描述。

二、Kotlin繼承類

Kotlin中,繼承這個特性除了定義關鍵字,以及所有的父類和Java語言不通之外,其他的其實無太大的差別。不過既然寫到了這裡,還是從始至終的寫完這個特性,如果您有Java的基礎,您可以當複習一遍。

2.1、超類(Any)

Kotlin中,說有的類都是繼承與Any類,這是這個沒有父型別的類。即當我們定義各類時,它預設是繼承與Any這個超類的

例:

class Demo    // 這裡定義了一個Demo類,即這個類預設是繼承與超類的。
複製程式碼

因為Any這個類只是給我們提供了equals()hashCode()toString()這三個方法。我們可以看看Any這個類的原始碼實現:

package kotlin

/**
 * The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass.
 * 看這個原始碼註釋:意思是任何一個Kotlin的類都繼承與這個[Any]類
 */
public open class Any {
    
    // 比較: 在平時的使用中經常用到的equals()函式的原始碼就在這裡額
    public open operator fun equals(other: Any?): Boolean
    
    // hashCode()方法:其作用是返回該物件的雜湊值
    public open fun hashCode(): Int
    
    // toString()方法
    public open fun toString(): String
}
複製程式碼

從原始碼可以我們看出,它直接屬於kotlin這個包下。並且只定義了上面所示的三個方法。或許你具有Java的程式設計經驗。在我們熟知的Java中,所有的類預設都是繼承與Object型別的。而Object這個類除了比Any多了幾個方法與屬性外,沒有太大的區別。不過他們並不是同一個類。這裡就不多種講解了....

從上面原始碼中所產生的疑惑:類與函式前面都加上了open這個修飾符。那麼這個修飾符的作用是什麼呢?
其實我們分析可以得出:既然Any類是所有類的父類,那麼我們自己要定義一個繼承類,跟著Any類的語法與結構就能定義一個繼承類。故而,open修飾符是我們定義繼承類的修飾符

2.2、定義

2.2.1、繼承類的基礎使用

  • 定義繼承類的關鍵字為:open。不管是類、還是成員都需要使用open關鍵字。

  • 定義格式為:

    open class 類名{
         ...
         open var/val 屬性名 = 屬性值
         ...
         open fun 函式名()
         ...
     }
    複製程式碼

例:這裡定義一個繼承類Demo,並實現兩個屬性與方法,並且定義一個DemoTest去繼承自Demo

open class Demo{

    open var num = 3

    open fun foo() = "foo"

    open fun bar() = "bar"

}

class DemoTest : Demo(){
    // 這裡值得注意的是:Kotlin使用繼承是使用`:`符號,而Java是使用extends關鍵字
}

fun main(args: Array<String>) {

    println(DemoTest().num)
    DemoTest().foo()
    DemoTest().bar()

}
複製程式碼

輸出結果為:

3
foo
bar
複製程式碼

分析:從上面的程式碼可以看出,DemoTest類只是繼承了Demo類,並沒有實現任何的程式碼結構。一樣可以使用Demo類中的屬性與函式。這就是繼承的好處。

2.2.2、繼承類的建構函式

在上一篇文章中,講解到了Kotlin類,可以有一個主建構函式,或者多個輔助函式。或者沒有建構函式的情況。如果您對Kotlin的建構函式還不瞭解的情況,請閱讀我的上一篇文章Kotlin——中級篇(一):類(class)詳解

這裡當實現類無主建構函式,和存在主建構函式的情況。

  • 無主建構函式

當實現類無主建構函式時,則每個輔助建構函式必須使用super關鍵字初始化基型別,或者委託給另一個建構函式。 請注意,在這種情況下,不同的輔助建構函式可以呼叫基型別的不同建構函式

例:這裡舉例在Android中常見的自定義View實現,我們熟知,當我們指定一個元件是,一般實現繼承類(基型別)的三個建構函式。

class MyView : View(){

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
        : super(context, attrs, defStyleAttr)
}
複製程式碼

可以看出,當實現類無主建構函式時,分別使用了super()去實現了基類的三個建構函式。

  • 存在主建構函式

當存在主建構函式時,主建構函式一般實現基型別中引數最多的建構函式,引數少的建構函式則用this關鍵字引用即可了。這點在Kotlin——中級篇(一):類(class)詳解這篇文章是講解到的。

例:同樣以自定義元件為例子

class MyView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int)
    : View(context, attrs, defStyleAttr) {

    constructor(context: Context?) : this(context,null,0)
    
    constructor(context: Context?,attrs: AttributeSet?) : this(context,attrs,0)
}
複製程式碼

2.3、函式的過載與重寫

Kotlin中關於函式的過載重寫,和Java中是幾乎是一樣的,但是這裡還是舉例來說明一下。

2.3.1、重寫函式中的兩點特殊用法

不管是Java還是Kotlin,重寫基型別裡面的方法,則稱為重寫,或者是覆蓋基型別方法。不過這裡介紹兩點Kotlin一點特殊的地方

  1. 當基類中的函式,沒有用open修飾符修飾的時候,實現類中出現的函式的函式名不能與基類中沒有用open修飾符修飾的函式的函式名相同,不管實現類中的該函式有無override修飾符修飾。讀著有點繞,直接看例子你就明白了。

例:

open class Demo{
    fun test(){}   // 注意,這個函式沒有用open修飾符修飾
}

class DemoTest : Demo(){
    
    // 這裡宣告一個和基型別無open修飾符修飾的函式,且函式名一致的函式
    // fun test(){}   編輯器直接報紅,根本無法執行程式
    // override fun test(){}   同樣報紅
}
複製程式碼
  1. 當一個類不是用open修飾符修飾時,這個類預設是final的。即:
class A{}

等價於

final class A{}   // 注意,則的`final`修飾符在編輯器中是灰色的,因為Kotlin中預設的類預設是final的
複製程式碼

那麼當一個基類去繼承另外一個基類時,第二個基類不想去覆蓋掉第一個基類的方法時,第二個基類的該方法使用final修飾符修飾。

例:

open class A{
    open fun foo(){}
}

// B這個類繼承類A,並且類B同樣使用open修飾符修飾了的
open class B : Demo(){
   
    // 這裡使用final修飾符修飾該方法,禁止覆蓋掉類A的foo()函式
    final override fun foo(){}
}
複製程式碼

2.3.2、方法過載

在文章的開頭提到了多型這個特性,方法的過載其實主要體現在這個地方。即函式名相同,函式的引數不同的情況。這一點和Java是相同的

這一點在繼承類中同樣有效:

例:

open class Demo{
    open fun foo() = "foo"
}

class DemoTest : Demo(){

    fun foo(str: String) : String{
        return str
    }

    override fun foo(): String {
        return super.foo()
    }
}    

fun main(args: Array<String>) {
    println(DemoTest().foo())
    DemoTest().foo("foo的過載函式")
}
複製程式碼

輸出結果為:

foo
foo的過載函式
複製程式碼

2.4、重寫屬性

  • 重寫屬性和重寫方法其實大致是相同的,但是屬性不能被過載。
  • 重寫屬性即指:在基類中宣告的屬性,然後在其基類的實現類中重寫該屬性,該屬性必須以override關鍵字修飾,並且其屬性具有和基類中屬性一樣的型別。且可以重寫該屬性的值(Getter

例:

open class Demo{
    open var num = 3
}

class DemoTest : Demo(){
    override var num: Int = 10
}
複製程式碼

2.4.1、重寫屬性中,val與var的區別

這裡可以看出重寫了num這個屬性,並且為這個屬性重寫了其值為10
但是,還有一點值得我們注意:當基類中屬性的變數修飾符為val的使用,其實現類可以用重寫屬性可以用var去修飾。反之則不能。

例:

open class Demo{
    open val valStr = "我是用val修飾的屬性"
}

class DemoTest : Demo(){

    /*
     * 這裡用val、或者var重寫都是可以的。
     * 不過當用val修飾的時候不能有setter()函式,編輯器直接會報紅的
     */
    
    // override val valStr: String
    //   get() = super.valStr

    // override var valStr: String = ""
    //   get() = super.valStr

    // override val valStr: String = ""

    override var valStr: String = "abc"
        set(value){field = value}
}

fun main(arge: Array<String>>){
    println(DemoTest().valStr)

    val demo = DemoTest()
    demo.valStr = "1212121212"
    println(demo.valStr)
}
複製程式碼

輸出結果為:

abc
1212121212
複製程式碼

2.4.2、Getter()函式慎用super關鍵字

在這裡值得注意的是,在實際的專案中在重寫屬性的時候不用get() = super.xxx,因為這樣的話,不管你是否重新為該屬性賦了新值,還是支援setter(),在使用的時候都呼叫的是基類中的屬性值。

例: 繼上面中的例子

class DemoTest : Demo(){

    /*
     * 這裡介紹重寫屬性是,getter()函式中使用`super`關鍵字的情況
     */
    
    override var valStr: String = "abc"、
        get() = super.valStr
        set(value){field = value}
}

fun main(arge: Array<String>>){
    println(DemoTest().valStr)

    val demo = DemoTest()
    demo.valStr = "1212121212"
    println(demo.valStr)
}
複製程式碼

輸出結果為:

我是用val修飾的屬性
我是用val修飾的屬性
複製程式碼

切記:重寫屬性的時候慎用super關鍵字。不然就是上面例子的效果

2.4.3、在主建構函式中重寫

這一點和其實在介面類的文章中講解過了,不清楚的可以去參見Kotlin——中級篇(五):列舉類(Enum)、介面類(Interface)詳解

例:基類還是上面的例子

class DemoTest2(override var num: Int, override val valStr: String) : Demo()

fun main(args: Array<String>){
    val demo2 = DemoTest2(1,"建構函式中重寫")
    println("num = ${demo2.num} \t valStr = ${demo2.valStr}")
}
複製程式碼

輸出結果為:

num = 1 	 valStr = 建構函式中重寫
複製程式碼

2.5、覆蓋規則

這裡的覆蓋規則,是指實現類繼承了一個基類,並且實現了一個介面類,當我的基類中的方法、屬性和介面類中的函式重名的情況下,怎樣去區分實現類到底實現哪一個中的屬性或屬性。 這一點和一個類同時實現兩個介面類,而兩個介面都用同樣的屬性或者函式的時候是一樣的。在介面類這篇文章中已經講解過,您可以參見Kotlin——中級篇(五):列舉類(Enum)、介面類(Interface)詳解

例:

open class A{
    open fun test1(){ println("基類A中的函式test1()") }

    open fun test2(){println("基類A中的函式test2()")}
}

interface B{
    fun test1(){ println("介面類B中的函式test1()") }

    fun test2(){println("介面類B中的函式test2()")}
}

class C : A(),B{
    override fun test1() {
        super<A>.test1()
        super<B>.test1()
    }

    override fun test2() {
        super<A>.test2()
        super<B>.test2()
    }
}
複製程式碼

總結

對於Kotlin繼承類這一個知識點,在專案中用到的地方是很常見的。當你認真的學習完上面的內容,我相信你可以能很輕易的用於專案中,不過對一個類來說,繼承的代價較高,當實現一個功能不必用到太多的整合屬性的時候,可以用物件表示式這一個高階功能去替代掉繼承。
如果你有過其他面嚮物件語言的程式設計經驗的話,你只要掌握其關鍵字、屬性/函式重寫、以及覆蓋規則這三三個知識點就可以了。

原始碼

如果各位大佬看了之後感覺還闊以,就請各位大佬隨便star一下,您的關注是我最大的動力。
我的個人部落格Jetictors
GithubJteictors

歡迎各位大佬進群共同研究、探索

QQ群號:497071402

Kotlin——中級篇(四):繼承類詳解

相關文章