Kotlin入门(14)继承的那些事儿
上一篇文章介紹了類對(duì)成員的聲明方式與使用過(guò)程,從而初步了解了類的成員及其運(yùn)用。不過(guò)早在《Kotlin入門(12)類的概貌與構(gòu)造》中,提到MainActivity繼承自AppCompatActivity,而Kotlin對(duì)于類繼承的寫法是“class MainActivity : AppCompatActivity() {}”,這跟Java對(duì)比有明顯差異,那么Kotlin究竟是如何定義基類并由基類派生出子類呢?為廓清這些迷霧,本篇文章就對(duì)類繼承的相關(guān)用法進(jìn)行深入探討。
?
博文《Kotlin入門(13)類成員的眾生相》在演示類成員時(shí)多次重寫了WildAnimal類,這下你興沖沖地準(zhǔn)備按照MainActivity的繼承方式,從WildAnimal派生出一個(gè)子類Tiger,寫好構(gòu)造函數(shù)的兩個(gè)輸入?yún)?shù),補(bǔ)上基類的完整聲明,敲了以下代碼不禁竊喜這么快就大功告成了:
class Tiger(name:String="老虎", sex:Int = 0) : WildAnimal(name, sex) { }誰(shuí)料編譯器無(wú)情地蹦出錯(cuò)誤提示“The type is final, so it cannot be inherited from”,意思是WildAnimal類是final類型,所以它不允許被繼承。原來(lái)Java默認(rèn)每個(gè)類都能被繼承,除非加了關(guān)鍵字final表示終態(tài),才不能被其它類繼承。Kotlin恰恰相反,它默認(rèn)每個(gè)類都不能被繼承(相當(dāng)于Java類被final修飾了),如果要讓某個(gè)類成為基類,則需把該類開放出來(lái),也就是添加關(guān)鍵字open作為修飾。因此,接下來(lái)還是按照Kotlin的規(guī)矩辦事,重新寫個(gè)采取open修飾的基類,下面即以鳥類Bird進(jìn)行演示,改寫后的基類代碼框架如下:
open class Bird (var name:String, val sex:Int = 0) {//此處暫時(shí)省略基類內(nèi)部的成員屬性和方法 }現(xiàn)在有了基類框架,還得往里面補(bǔ)充成員屬性和成員方法,然后給這些成員添加開放性修飾符。就像大家在Java和C++世界中熟知的幾個(gè)關(guān)鍵字,包括public、protected、private,分別表示公開、只對(duì)子類開放、私有。那么Kotlin體系參照J(rèn)ava世界也給出了四個(gè)開放性修飾符,按開放程度從高到低分別是:
public : 對(duì)所有人開放。Kotlin的類、函數(shù)、變量不加開放性修飾符的話,默認(rèn)就是public類型。
internal : 只對(duì)本模塊內(nèi)部開放,這是Kotlin新增的關(guān)鍵字。對(duì)于App開發(fā)來(lái)說(shuō),本模塊便是指App自身。
protected : 只對(duì)自己和子類開放。
private : 只對(duì)自己開放,即私有。
注意到這幾個(gè)修飾符與open一樣都加在類和函數(shù)前面,并且都包含“開放”的意思,乍看過(guò)去還真有點(diǎn)撲朔迷離,到底o(hù)pen跟四個(gè)開放性修飾符是什么關(guān)系?其實(shí)也不復(fù)雜,open不控制某個(gè)對(duì)象的訪問(wèn)權(quán)限,只決定該對(duì)象能否繁衍開來(lái),說(shuō)白了,就是公告這個(gè)家伙有沒(méi)有資格生兒育女。只有頭戴open帽子的類,才允許作為基類派生出子類來(lái);而頭戴open帽子的函數(shù),表示它允許在子類中進(jìn)行重寫。
至于那四個(gè)開放性修飾符,則是用來(lái)限定允許訪問(wèn)某對(duì)象的外部范圍,通俗地說(shuō),就是哪里的男人可以娶這個(gè)美女。頭戴public的,表示全世界的男人都能娶她;頭戴internal的,表示本國(guó)的男人可以娶她;頭戴protected的,表示本單位以及下屬單位的男人可以娶她;頭戴private的,表示肥水不流外人田,只有本單位的帥哥才能娶這個(gè)美女噢。
因?yàn)閜rivate的限制太嚴(yán)厲了,只對(duì)自己開放,甚至都不允許子類染指,所以它跟關(guān)鍵字open勢(shì)同水火。open表示這個(gè)對(duì)象可以被繼承,或者可以被重載,然而private卻堅(jiān)決斬?cái)嘣搶?duì)象與其子類的任何關(guān)系,因此二者不能并存。倘若在代碼中強(qiáng)行給某個(gè)方法同時(shí)加上open和private,編譯器只能無(wú)奈地報(bào)錯(cuò)“Modifier 'open' is incompatible with 'private'”,意思是open與private不兼容。
按照以上的開放性相關(guān)說(shuō)明,接下來(lái)分別給Bird類的類名、函數(shù)名、變量名加上修飾符,改寫之后的基類代碼是下面這樣:
好不容易鼓搗出來(lái)一個(gè)正兒八經(jīng)的鳥兒基類,再來(lái)聲明一個(gè)它的子類試試,例如鴨子是鳥類的一種,于是下面有了鴨子的類定義代碼:
//注意父類Bird已經(jīng)在構(gòu)造函數(shù)聲明了屬性,故而子類Duck無(wú)需重復(fù)聲明屬性 //也就是說(shuō),子類的構(gòu)造函數(shù),在輸入?yún)?shù)前面不要再加val和var class Duck(name:String="鴨子", sex:Int = Bird.MALE) : Bird(name, sex) { }子類也可以定義新的成員屬性和成員方法,或者重寫被聲明為open的父類方法。比方說(shuō)性別名稱“公”和“母”一般用于家禽,像公雞、母雞、公鴨、母鴨等等,指代野生鳥類的性別則通常使用“雄”和“雌”,所以定義野生鳥類的時(shí)候,就得重寫獲取性別名稱的getSexName方法,把“公”和“母”的返回值改為“雄”和“雌”。方法重寫之后,定義了鴕鳥的類代碼如下所示:
class Ostrich(name:String="鴕鳥", sex:Int = Bird.MALE) : Bird(name, sex) {//繼承protected的方法,標(biāo)準(zhǔn)寫法是“override protected”//override protected fun getSexName(sex:Int):String {//不過(guò)protected的方法繼承過(guò)來(lái)默認(rèn)就是protected,所以也可直接省略protected//override fun getSexName(sex:Int):String {//protected的方法繼承之后允許將可見性升級(jí)為public,但不能降級(jí)為privateoverride public fun getSexName(sex:Int):String {return if(sex==MALE) "雄" else "雌"} }
除了上面講的普通類繼承,Kotlin也存在與Java類似的抽象類,抽象類之所以存在,是因?yàn)槠鋬?nèi)部擁有被abstract修飾的抽象方法。抽象方法沒(méi)有具體的函數(shù)體,故而外部無(wú)法直接聲明抽象類的實(shí)例;只有在子類繼承之時(shí)重寫抽象方法,該子類方可正常聲明對(duì)象實(shí)例。舉個(gè)例子,雞屬于鳥類,可公雞和母雞的叫聲是不一樣的,公雞是“喔喔喔”地叫,而母雞是“咯咯咯”地叫;所以雞這個(gè)類的叫喚方法callOut,發(fā)出什么聲音并不確定,只能先聲明為抽象方法,連帶著雞類Chicken也變成抽象類了。根據(jù)上述的抽象類方案,定義好的Chicken類代碼示例如下:
//子類的構(gòu)造函數(shù),原來(lái)的輸入?yún)?shù)不用加var和val,新增的輸入?yún)?shù)必須加var或者val。 //因?yàn)槌橄箢惒荒苤苯邮褂?#xff0c;所以構(gòu)造函數(shù)不必給默認(rèn)參數(shù)賦值。 abstract class Chicken(name:String, sex:Int, var voice:String) : Bird(name, sex) {val numberArray:Array<String> = arrayOf("一","二","三","四","五","六","七","八","九","十");//抽象方法必須在子類進(jìn)行重寫,所以可以省略關(guān)鍵字open,因?yàn)閍bstract方法默認(rèn)就是open類型//open abstract fun callOut(times:Int):Stringabstract fun callOut(times:Int):String }接著從Chicken類派生出公雞類Cock,指定公雞的聲音為“喔喔喔”,同時(shí)還要重寫callOut方法,明確公雞的叫喚行為。具體的Cock類代碼如下所示:
class Cock(name:String="雞", sex:Int = Bird.MALE, voice:String="喔喔喔") : Chicken(name, sex, voice) {override fun callOut(times: Int): String {var count = when {//when語(yǔ)句判斷大于和小于時(shí),要把完整的判斷條件寫到每個(gè)分支中times<=0 -> 0times>=10 -> 9else -> times}return "$sexName$name${voice}叫了${numberArray[count]}聲,原來(lái)它在報(bào)曉呀。"} }同樣派生而來(lái)的母雞類Hen,也需指定母雞的聲音“咯咯咯”,并重寫callOut叫喚方法,具體的Hen類代碼如下所示:
class Hen(name:String="雞", sex:Int = Bird.FEMALE, voice:String="咯咯咯") : Chicken(name, sex, voice) {override fun callOut(times: Int): String {var count = when {times<=0 -> 0times>=10 -> 9else -> times}return "$sexName$name${voice}叫了${numberArray[count]}聲,原來(lái)它下蛋了呀。"} }定義好了callOut方法,外部即可調(diào)用Cock類和Hen類的該方法了,調(diào)用代碼示例如下:
//調(diào)用公雞類的叫喚方法tv_class_inherit.text = Cock().callOut(count++%10)//調(diào)用母雞類的叫喚方法tv_class_inherit.text = Hen().callOut(count++%10)
既然提到了抽象類,就不得不提接口interface。Kotlin的接口與Java一樣是為了間接實(shí)現(xiàn)多重繼承,由于直接繼承多個(gè)類可能存在方法沖突等問(wèn)題,因此Kotlin在編譯階段就不允許某個(gè)類同時(shí)繼承多個(gè)基類,否則會(huì)報(bào)錯(cuò)“Only one class may appear in a supertype list”。于是乎,通過(guò)接口定義幾個(gè)抽象方法,然后在實(shí)現(xiàn)該接口的具體類中重寫這幾個(gè)方法,從而間接實(shí)現(xiàn)C++多重繼承的功能。
在Kotlin中定義接口需要注意以下幾點(diǎn):
1、接口不能定義構(gòu)造函數(shù),否則編譯器會(huì)報(bào)錯(cuò)“An interface may not have a constructor”;
2、接口的內(nèi)部方法通常要被實(shí)現(xiàn)它的類進(jìn)行重寫,所以這些方法默認(rèn)為抽象類型;
3、與Java不同的是,Kotlin允許在接口內(nèi)部實(shí)現(xiàn)某個(gè)方法,而Java接口的所有內(nèi)部方法都必須是抽象方法;
Android開發(fā)最常見的接口是控件的點(diǎn)擊監(jiān)聽器View.OnClickListener,其內(nèi)部定義了控件的點(diǎn)擊動(dòng)作onClick,類似的還有長(zhǎng)按監(jiān)聽器View.OnLongClickListener、選擇監(jiān)聽器CompoundButton.OnCheckedChangeListener等等,它們無(wú)一例外都定義了某種行為的事件處理過(guò)程。對(duì)于本文的鳥類例子而言,也可通過(guò)一個(gè)接口定義鳥兒的常見動(dòng)作行為,譬如鳥兒除了叫喚動(dòng)作,還有飛翔、游泳、奔跑等等動(dòng)作,有的鳥類擅長(zhǎng)飛翔(如大雁、老鷹),有的鳥類擅長(zhǎng)游泳(如鴛鴦、鸕鶿),有的鳥類擅長(zhǎng)奔跑(如鴕鳥、鴯鹋)。因此針對(duì)鳥類的飛翔、游泳、奔跑等動(dòng)作,即可聲明Behavior接口,在該接口中定義幾個(gè)行為方法如fly、swim、run,下面是一個(gè)定義好的行為接口代碼例子:
那么其他類實(shí)現(xiàn)Behavior接口時(shí),跟類繼承一樣把接口名稱放在冒號(hào)后面,也就是說(shuō),Java的extends和implement這兩個(gè)關(guān)鍵字在Kotlin中都被冒號(hào)取代了。然后就像重寫抽象類的抽象方法一樣,重寫該接口的抽象方法,以鵝的Goose類為例,重寫接口方法之后的代碼如下所示:
class Goose(name:String="鵝", sex:Int = Bird.MALE) : Bird(name, sex), Behavior {override fun fly():String {return "鵝能飛一點(diǎn)點(diǎn),但飛不高,也飛不遠(yuǎn)。"}override fun swim():String {return "鵝,鵝,鵝,曲項(xiàng)向天歌。白毛浮綠水,紅掌撥清波。"}//因?yàn)榻涌谝呀?jīng)實(shí)現(xiàn)了run方法,所以此處可以不用實(shí)現(xiàn)該方法,當(dāng)然你要實(shí)現(xiàn)它也行。override fun run():String {//super用來(lái)調(diào)用父類的屬性或方法,由于Kotlin的接口允許實(shí)現(xiàn)方法,因此super所指的對(duì)象也可以是interfacereturn super.run()}//重載了來(lái)自接口的抽象屬性override var skilledSports:String = "游泳" }這下大功告成,Goose類聲明的群鵝不但具備鳥類的基本功能,而且能飛、能游、能跑,活脫脫一只栩栩如生的大白鵝呀:
btn_interface_behavior.setOnClickListener {tv_class_inherit.text = when (count++%3) {0 -> Goose().fly()1 -> Goose().swim()else -> Goose().run()}}
總結(jié)一下,Kotlin的類繼承與Java相比有所不同,首先Kotlin的類默認(rèn)不可被繼承,如需繼承則要添加open聲明;而Java的類默認(rèn)是允許被繼承的,只有添加final聲明才表示不能被繼承。其次,Kotlin除了常規(guī)的三個(gè)開放性修飾符public、protected、private,另外增加了修飾符internal表示只對(duì)本模塊開放。再次,Java的類繼承關(guān)鍵字extends,以及接口實(shí)現(xiàn)關(guān)鍵字implement,在Kotlin中都被冒號(hào)所取代。最后,Kotlin允許在接口內(nèi)部實(shí)現(xiàn)某個(gè)方法,而Java接口的內(nèi)部方法只能是抽象方法。
__________________________________________________________________________
本文現(xiàn)已同步發(fā)布到微信公眾號(hào)“老歐說(shuō)安卓”,打開微信掃一掃下面的二維碼,或者直接搜索公眾號(hào)“老歐說(shuō)安卓”添加關(guān)注,更快更方便地閱讀技術(shù)干貨。
?
轉(zhuǎn)載于:https://www.cnblogs.com/aqi00/p/7380070.html
總結(jié)
以上是生活随笔為你收集整理的Kotlin入门(14)继承的那些事儿的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 论道社会化商业
- 下一篇: 10.6-全栈Java笔记:常见流详解(