kotlin入門潛修之類和對象篇—繼承

本文收錄于 kotlin入門潛修專題系列辛臊,歡迎學(xué)習(xí)交流仙粱。

創(chuàng)作不易,如有轉(zhuǎn)載彻舰,還請備注伐割。

繼承

面向?qū)ο蟮娜蠡豪^承、多態(tài)與封裝刃唤。這三個特性構(gòu)成了絢麗多彩的編程世界隔心,也衍生出了諸多優(yōu)雅的設(shè)計。本篇文章將會解析kotlin中的繼承機(jī)制尚胞。

眾所周知硬霍,java中所有的類都會默認(rèn)繼承java.lang.Object類,同樣笼裳,kotlin中所有的類也默認(rèn)繼承了一個叫做Any的類唯卖,其作用同java的Object類,是kotlin里面所有類的基類躬柬。

需要注意的是拜轨,Any類雖然同java中的Object類一樣作為所有類的基類存在,但是Any類并不等同于java的Object類允青,因?yàn)锳ny類中只有equals橄碾、hasCode、toString三個方法昧廷,而java中的Object類還有諸如getClass堪嫂、notifyAll、wait木柬、clone等方法皆串,所以二者并不是一個類。

kotlin中的繼承寫法也和java完全不一樣了眉枕,kotlin中不再有extends恶复、implements關(guān)鍵字怜森,取而代之的是冒號“ : ”,其定義如下:

open class Person constructor(name: String) {//基類谤牡,注意有open關(guān)鍵字修飾
}
class Student(name: String) : Person(name) {//子類副硅,子類必須要實(shí)現(xiàn)父類中的一個構(gòu)造方法
}

有幾點(diǎn)需要注意:

  1. kotlin中的類默認(rèn)是final的,即是無法繼承的翅萤,這與java不同恐疲,java中默認(rèn)都是可繼承的。kotlin中所有的設(shè)計都是要顯示提供套么,其實(shí)這也正是kotlin的設(shè)計理念培己,只有在真正需要的時候才暴露。kotlin提供了open關(guān)鍵字用于顯示表明該類是可繼承的胚泌。
  2. 子類必須要實(shí)現(xiàn)父類中的一個構(gòu)造方法省咨。可以通過子類的主構(gòu)造方法去初始化父類構(gòu)造方法玷室,也可以通過第二構(gòu)造方法初始化父類的構(gòu)造方法零蓉。上面的例子就是通過主構(gòu)造方法初始化了父類。第二構(gòu)造方法初始化示例如下:
//父類People穷缤,注意敌蜂,這里提供了一個主構(gòu)造方法和一個第二構(gòu)造方法
open class People constructor(name: String) {
    public constructor(name: String, age: Int) : this(name)
}
//下面是幾種不同的初始化父類的寫法
//1. 通過第二構(gòu)造方法初始化,這里調(diào)用了父類People的主構(gòu)造方法
class Teacher : People {
    constructor() : super("張三")
}
//2. 通過第二構(gòu)造方法初始化绅项,這里調(diào)用了父類People的第二構(gòu)造方法
class Teacher : People {
    constructor() : super("張三", 10)
}
//3.通過主構(gòu)造方法初始化紊册,這里調(diào)用了父類People的主構(gòu)造方法
class Teacher(name: String) : People (name){
}
//4.通過主構(gòu)造方法初始化比肄,這里調(diào)用了父類People的第二構(gòu)造方法
class Teacher(name: String) : People (name, 20){
}

在實(shí)際編碼中快耿,具體采用上面哪種寫法可以根據(jù)場景自行選擇。主要能夠保證初始化父類的任意構(gòu)造方法即可芳绩。

復(fù)寫方法(Overriding Methods)

kotlin中方法的復(fù)寫和類的設(shè)計理念一樣(類必須顯示定義為open才能被繼承)掀亥,必須要顯示指定該方法可以復(fù)寫,子類才能進(jìn)行復(fù)寫(當(dāng)然前提是父類也必須定義為可繼承的妥色,即要open修飾)搪花,其顯示指定的關(guān)鍵字依然是open。示例如下:

//父類嘹害,open修飾撮竿,表示可繼承
open class Person {
    fun getAge(){}//注意這里沒有open關(guān)鍵字
    open fun getName(){}//這里有open關(guān)鍵字
}
class Student() : Person() {
    override fun getName() {//這里override是合法的,因?yàn)楦割愒摲椒ㄊ褂昧薿pen修飾笔呀,表示可以被復(fù)寫
        super.getName()
    }
    override fun getAge(){}//!!! 這是不合法的幢踏,編譯不通過!因?yàn)楦割愔械膅etAge()并沒有顯示指定為open
    fun getAge(){}//!!! 這也是不合法的许师,編譯不通過房蝉!因?yàn)楦割愔幸呀?jīng)存在getAge()僚匆,只能override。在這個例子中即使override也是不合法的搭幻,上面已經(jīng)闡述咧擂。
}

一個方法一旦被標(biāo)記為open方法,那么該方法就一直能被override(即其子類的子類的子類...等等都可以復(fù)寫)檀蹋,那么如果子類不想再讓其子類override方法怎么辦松申?比如上個例子中,Person中的getName是可被override的俯逾,所以子類Student可以通過override fun getName來復(fù)寫攻臀,但是現(xiàn)在Student不在期望其子類再override getName方法,該怎么辦纱昧?很簡單刨啸,在其方法前加final關(guān)鍵字即可:

open  class Student() : Person() {
    final override fun getName() {//注意這里加了final關(guān)鍵字,表示其子類不再能復(fù)寫該方法识脆。
        super.getName()
    }
}

復(fù)寫屬性(overriding properties)

復(fù)寫屬性和復(fù)寫方法一樣设联,要用open顯示標(biāo)明可復(fù)寫。屬性的繼承有幾點(diǎn)需要注意的灼捂,示例如下

//父類离例,該類設(shè)置為了可繼承,即open修飾
open class Person {
    var age : Int = 20
    var height: Int = 170
    open var address : String = "address"
    val name : String = "name"
    open val email : String = "email"
    open val phoneNum : Int = 1234567
    open var score: Int = 80
    open val sex : String get() {return "男"}
}
//子類悉稠,繼承Person宫蛆,分析的重點(diǎn)就在這里。
class Student : Person() {
    //首先看var變量
    var age: Int = 20//!!!編譯不通過的猛,父類已經(jīng)存在該字段耀盗。
    override var height: Int = 180//!!!編譯不通過,因?yàn)楦割愔袥]有顯示定義為open卦尊,故不能復(fù)寫叛拷。
    override var address: String = "address"http://正確,因?yàn)楦割愔酗@示定義為了open
    //下面是val變量
    val name: String = "name"http://!!!編譯不通過岂却,父類已經(jīng)存在該字段忿薇。
    override val email: String = "email"http://正確,因?yàn)楦割愔酗@示定義為了open
    override var phoneNum : Int = 1234567//正確躏哩,注意署浩,這里父類中的phoneNum是val不可變的,但這里復(fù)寫為了var可變的扫尺,kotlin是允許這么做的筋栋。
    override val score: Int = 80//!!!編譯錯誤,注意器联,這里父類中的score是var可變的二汛,而這里復(fù)寫為了val不可變的婿崭,kotlin中是不允許這么做的。
    override val sex: String get() {//正確肴颊,這里只是演示了屬性變量另一種初始化方法氓栈,即使用get方法。
        return "男"
    }
}

上面基本分析了復(fù)寫屬性的各種情況婿着,唯一需要注意的是父類中的val是可以在子類中被復(fù)寫為var的授瘦,反之則不行。這是為什么竟宋?

是這樣的提完,kotlin中的val屬性都默認(rèn)定義了一個getter方法,子類復(fù)寫為var的時候?qū)嶋H上是為該變量額外增加了一個setter方法丘侠,所以可以這么做徒欣。

此外,kotlin也可以在主構(gòu)造方法中復(fù)寫屬性蜗字,如下所示:

open class Person constructor(open val name: String) {
}
//注意打肝,子類在主構(gòu)造方法中復(fù)寫了name屬性
open class Student(override val name: String) : Person(name) {
}

派生類的初始化順序

所謂派生類即是繼承父類的子類。那么派生類的執(zhí)行順序是怎么樣的挪捕?先看下面一個例子:

//父類
open class Person(name: String) {
    init {
        println("initializing person")
    }

//這里運(yùn)用了let方法粗梭,會在后續(xù)文章中分析
    open val nameLength: Int = name.length.let {
        println("initializing name length in person:".plus(it))
        it
    }
}
//子類
class Student(name: String, lastName: String) : Person(name.let { println("argument for person $it")
            it }) {
    init {
        println("initializing student")
    }//注意,這里看著比較繞级零,但是實(shí)際完成功能就是打印基類的入?yún)?
    override val nameLength: Int = lastName.length.let {
        (super.nameLength + it).let {
            println("initializing name length in student:".plus(it))
            it
        }
    }
}
   //程序執(zhí)行入口
    @JvmStatic fun main(args: Array<String>) {
            var student = Student("name", "lastName")//生成student對象
     }

上面代碼執(zhí)行main方法后断医,會打印一下日志:

argument for person name
initializing person
initializing name length in person:4
initializing student
initializing name length in student:12

通過日志打印可以看出,kotlin會首先初始化父類奏纪,父類先執(zhí)行構(gòu)造方法鉴嗤,然后按編碼順序先后執(zhí)行init塊、屬性初始化等亥贸,接著會執(zhí)行子類構(gòu)造方法躬窜、init塊浇垦、屬性初始化等炕置。

由此可知,在父類執(zhí)行構(gòu)造方法的時候男韧,子類的屬性或者復(fù)寫父類的屬性都還沒有初始化朴摊,所以父類中一定不能使用這些屬性,否則會造成未知的錯誤此虑,甚至?xí)斐蛇\(yùn)行時異常甚纲。

因此,在設(shè)計父類的時候朦前,一定要避免在構(gòu)造方法介杆、屬性初始化以及init塊中使用open類型的成員變量(因?yàn)檫@些晚些時候可能會被子類復(fù)寫)鹃操。

調(diào)用父類中的實(shí)現(xiàn)

kotlin同java一樣,子類要調(diào)用父類的實(shí)現(xiàn)可以通過super關(guān)鍵字完成春哨,示例如下:

//父類
open class Person() {
    open fun printSex() {
        println("默認(rèn)性別:男")
    }
    var defaultName = ""
    open val age = 20
}
//子類
class Student() : Person() {
    override fun printSex() {//復(fù)寫父類printSex方法
        super.printSex()//這里通過super調(diào)用父類中方法
        println("the student age: 18")
    }
   fun printName(){//子類自定義打印姓名的方法
        println(super.defaultName)//這里直接調(diào)用了父類中的非open屬性荆隘。
    }
    override val age: Int
        get() = super.age + 2//這里通過super調(diào)用父類中的open屬性
}

kotlin中,只要父類中的實(shí)現(xiàn)(屬性或者方法)不是private的赴背,子類都可以通過super來調(diào)用父類的實(shí)現(xiàn)椰拒。

復(fù)寫規(guī)則

這里的復(fù)寫規(guī)則講的是,當(dāng)一個子類實(shí)現(xiàn)多個父實(shí)現(xiàn)的時候凰荚,會存在多個父實(shí)現(xiàn)含有相同實(shí)現(xiàn)的情形(如含有相同的方法簽名或者相同的屬性)燃观。注意,kotlin同java一樣便瑟,依然是單繼承體系缆毁,即一個子類一次只能繼承一個父類,這里所說的父實(shí)現(xiàn)是指到涂,子類可能會在繼承父類的同時實(shí)現(xiàn)了一個或者多個接口积锅。具體示例如下:

//父類A,有m1和m2兩個方法
open class A {
    open fun m1() {
        print("m1 in A")
    }

    open fun m2() {
        print("m2 in A")
    }
}
//接口B养盗,有m1和m3兩個方法缚陷,注意m1方法和A中的簽名一樣。
interface B {//kotlin中接口的寫法往核,使用關(guān)鍵字interface修飾
    fun m1() {//接口中的方法默認(rèn)都是open的箫爷,所以不需要使用open修飾
        print("m1 in B")
    }

    fun m3() {
        print("m3 in B")
    }
}
//實(shí)現(xiàn)類C,繼承了A同時實(shí)現(xiàn)了B接口
class C : A(), B {//多個實(shí)現(xiàn)的寫法使用英文逗號(,)隔開
        //注意這里聂儒,因?yàn)锳類中有方法m1,B接口中也有方法m1虎锚,所以子類就不知道該默認(rèn)實(shí)現(xiàn)哪個父實(shí)現(xiàn)中的方法。因此衩婚,在這種情形下窜护,kotlin會強(qiáng)制子類明確復(fù)寫該方法。如果子類還想調(diào)用父類的實(shí)現(xiàn)非春,那么可以通過super<父類型>這種方法來指定調(diào)用父類的實(shí)現(xiàn)柱徙,
        override fun m1() {//該方法必須要復(fù)寫
        super<A>.m1()//這里調(diào)用A類中m1的實(shí)現(xiàn),非強(qiáng)制奇昙,可選擇性調(diào)用
        super<B>.m1()//調(diào)用B接口中m1的實(shí)現(xiàn)
    }
}

上面代碼中护侮,由于m1存在實(shí)現(xiàn)沖突(兩個父實(shí)現(xiàn)都有該方法),所以子類必須要復(fù)寫該方法储耐,而m2羊初、m3不存在沖突,故kotlin不強(qiáng)制復(fù)寫什湘。

抽象類

kotlin中的抽象類同java一樣长赞,都是使用abstract關(guān)鍵字來修飾晦攒。kotlin中的抽象類,默認(rèn)都是open的得哆,所以不需要再顯示使用open關(guān)鍵字進(jìn)行修飾勤家。如果一個類的任意一個成員被定義為abstract,那么該類必須要定義為抽象類柳恐。

示例如下:

abstract class A {//抽象類使用abstract修飾
    abstract fun m1()//抽象方法不能有任何實(shí)現(xiàn)伐脖,即不能有方法體{}
    open fun m3() {//抽象類可以包含普通的方法實(shí)現(xiàn)
        print("m3 in A")
    }
}
//子類C,繼承抽象類A
class C : A() {
   //子類必須要實(shí)現(xiàn)抽象類中的抽象方法乐设。普通方法則不強(qiáng)制實(shí)現(xiàn)讼庇。
    override fun m1() {
    }
}

伴隨對象

伴隨對象是kotlin中特有的存在。kotlin不像java近尚、c#蠕啄,它沒有static方法,而是推薦使用包級別(package-level)的方法替代戈锻,示例如下:

package com.test//com.test包
fun staticM1(){//直接定義了一個staticM1方法歼跟,注意這里并沒有定義任何類
    println("staticM1")
}
//在Main類中調(diào)用該包級別方法
import com.test.staticM1//導(dǎo)入了staticM1方法
class Main {
    companion object {//這個是個伴隨對象,下面會分析
        @JvmStatic fun main(args: Array<String>) {
            staticM1()//這里調(diào)用了staticM1格遭,使用方法如同java中的static哈街,沒有生成任何類對象
        }

    }
}

上面的寫法即是包級別的方法,大部分都可以滿足要使用“靜態(tài)方法”的需求拒迅。從代碼也可以看出骚秦,包級別的方法不依附于任何類,也就是不屬于任何類璧微。但是假如有個方法需要在一個類中定義作箍,而我們確實(shí)又需要在不生成該類實(shí)例的情況下使用該方法,該怎么辦呢(如工廠方法模式)前硫?

針對這種情況胞得,kotlin提供了另一個實(shí)現(xiàn)機(jī)制:伴隨對象。有了伴隨對象屹电,就可以想調(diào)用靜態(tài)方法一樣使用了阶剑,如下所示:

class A {
    companion object {//伴隨對象的寫法,兩個關(guān)鍵字companion object
        fun m1() {//這里定義了一個m1方法嗤详,注意下面B類中的調(diào)用方式
            println("method m1 in A's companion object")
        }
    }
}

class B {
    fun test() {
        A.m1()//注意這里个扰,通過A類名調(diào)用了m1方法,而沒有生成A類實(shí)例
    }
}

實(shí)際上葱色,我們前面已經(jīng)多次用到伴隨對象了,比如程序的執(zhí)行入口Main類中main方法的實(shí)現(xiàn)娘香。我們都知道java中的執(zhí)行入口是靜態(tài)方法苍狰,那么kotlin中的執(zhí)行入口該怎么寫呢办龄?示例如下:

class Main {
    companion object {//伴隨對象
        @JvmStatic fun main(args: Array<String>) {//main方法執(zhí)行入口
        }
    }
}

當(dāng)然,也可以提供包級別的main方法淋昭,如下所示:

class Main {
//作為對比俐填,這里暫時注釋掉了伴隨對象
//    companion object {
//        @JvmStatic fun main(args: Array<String>) {
//
//        }
//
//    }
}
//這里提供了package-level的main入口方法,作用同上面注釋掉的伴隨對象寫法翔忽。
fun main(args: Array<String>) {
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末英融,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子歇式,更是在濱河造成了極大的恐慌驶悟,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件材失,死亡現(xiàn)場離奇詭異痕鳍,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)龙巨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門笼呆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人旨别,你說我怎么就攤上這事诗赌。” “怎么了秸弛?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵境肾,是天一觀的道長。 經(jīng)常有香客問我胆屿,道長奥喻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任非迹,我火速辦了婚禮环鲤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘憎兽。我一直安慰自己冷离,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布纯命。 她就那樣靜靜地躺著西剥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪亿汞。 梳的紋絲不亂的頭發(fā)上瞭空,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼咆畏。 笑死南捂,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的旧找。 我是一名探鬼主播溺健,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钮蛛!你這毒婦竟也來了鞭缭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤魏颓,失蹤者是張志新(化名)和其女友劉穎岭辣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琼开,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡易结,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了柜候。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搞动。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖渣刷,靈堂內(nèi)的尸體忽然破棺而出鹦肿,到底是詐尸還是另有隱情,我是刑警寧澤辅柴,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布箩溃,位于F島的核電站,受9級特大地震影響碌嘀,放射性物質(zhì)發(fā)生泄漏涣旨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一股冗、第九天 我趴在偏房一處隱蔽的房頂上張望霹陡。 院中可真熱鬧,春花似錦止状、人聲如沸烹棉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浆洗。三九已至,卻和暖如春集峦,著一層夾襖步出監(jiān)牢的瞬間伏社,已是汗流浹背抠刺。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洛口,地道東北人矫付。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓凯沪,卻偏偏與公主長得像第焰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子妨马,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Kotlin的類和接口與Java的類和接口是有一定的區(qū)別的烘跺。Kotlin的接口是可以包含屬性聲明湘纵。Kotlin默認(rèn)...
    程自舟閱讀 10,338評論 0 11
  • 前言 人生苦多,快來 Kotlin 滤淳,快速學(xué)習(xí)Kotlin梧喷! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,209評論 9 118
  • 寫在開頭:本人打算開始寫一個Kotlin系列的教程,一是使自己記憶和理解的更加深刻屁擅,二是可以分享給同樣想學(xué)習(xí)Kot...
    胡奚冰閱讀 1,422評論 5 11
  • 面向?qū)ο缶幊蹋∣OP) 在前面的章節(jié)中派歌,我們學(xué)習(xí)了Kotlin的語言基礎(chǔ)知識弯囊、類型系統(tǒng)、集合類以及泛型相關(guān)的知識胶果。...
    Tenderness4閱讀 4,441評論 1 6
  • 對于回顧這段歷史匾嘱。 用戰(zhàn)爭,屠殺早抠,血腥霎烙,只會引來更多的仇恨。這是經(jīng)歷過這段歷史的所有人都想避免的贝或。 美麗人生吼过,最悲...
    JabinW閱讀 195評論 0 0