Kotlin 知識(shí)梳理(3) - 類限次、對(duì)象和接口

Kotlin 知識(shí)梳理系列文章

Kotlin 知識(shí)梳理(1) - Kotlin 基礎(chǔ)
Kotlin 知識(shí)梳理(2) - 函數(shù)的定義與調(diào)用
Kotlin 知識(shí)梳理(3) - 類芒涡、對(duì)象和接口
Kotlin 知識(shí)梳理(4) - 數(shù)據(jù)類柴灯、類委托 及 object 關(guān)鍵字
Kotlin 知識(shí)梳理(5) - lambda 表達(dá)式和成員引用
Kotlin 知識(shí)梳理(6) - Kotlin 的可空性
Kotlin 知識(shí)梳理(7) - Kotlin 的類型系統(tǒng)
Kotlin 知識(shí)梳理(8) - 運(yùn)算符重載及其他約定
Kotlin 知識(shí)梳理(9) - 委托屬性
Kotlin 知識(shí)梳理(10) - 高階函數(shù):Lambda 作為形參或返回值
Kotlin 知識(shí)梳理(11) - 內(nèi)聯(lián)函數(shù)
Kotlin 知識(shí)梳理(12) - 泛型類型參數(shù)


一、本文概要

本文是對(duì)<<Kotlin in Action>>的學(xué)習(xí)筆記费尽,如果需要運(yùn)行相應(yīng)的代碼可以訪問在線環(huán)境 try.kotlinlang.org赠群,這部分的思維導(dǎo)圖為:

二、定義類繼承結(jié)構(gòu)

2.1 Kotlin 中的接口

Kotlin的接口可以包含以下兩種類型的方法:

  • 簡單的抽象方法
  • 包含默認(rèn)實(shí)現(xiàn)的抽象方法

簡單接口

  • 一個(gè)簡單的Kotlin接口使用 interface 關(guān)鍵字來聲明旱幼,所有實(shí)現(xiàn)這個(gè)接口的非抽象類都需要實(shí)現(xiàn)接口中定義的抽象方法查描。
  • Kotlin在類名后面使用 冒號(hào) 代替了Java中的extendsimplements關(guān)鍵字,一個(gè)類可以實(shí)現(xiàn)多個(gè)接口柏卤,但是只能繼承一個(gè)類冬三。
  • override修飾符用來標(biāo)注被重寫的父類或者接口的方法和屬性,并且是 強(qiáng)制要求 的缘缚。

下面的例子中定義了一個(gè)接口勾笆,并演示了如何實(shí)現(xiàn)該接口,以及接口中定義的抽象方法:


在接口中定義方法的默認(rèn)實(shí)現(xiàn)

我們可以給接口的方法提供一個(gè)默認(rèn)的實(shí)現(xiàn)桥滨,定義的方法和普通函數(shù)相同窝爪。



如果一個(gè)類實(shí)現(xiàn)了兩個(gè)接口,而這兩個(gè)接口定義了相同的方法齐媒,并且都提供了該方法的默認(rèn)實(shí)現(xiàn)蒲每,那么該類必須顯示實(shí)現(xiàn)該方法,否則會(huì)在編譯時(shí)報(bào)錯(cuò):


調(diào)用繼承自接口的方法的實(shí)現(xiàn)

當(dāng)需要調(diào)用一個(gè)繼承的實(shí)現(xiàn)喻括,可以使用與Java相同的關(guān)鍵字 super邀杏,并在后面的尖括號(hào)中指明父類的名字,最后是調(diào)用的方法名

2.2 訪問性修飾符:open唬血、final望蜡、abstract

一般類

  • Kotlin中,類和方法默認(rèn)都是final的刁品,如果想允許創(chuàng)建一個(gè)類的子類,需要使用open修飾符來標(biāo)示這個(gè)類浩姥,此外還需要給每一個(gè)允許被重寫的屬性或方法添加open修飾符挑随。
  • 如果重寫了一個(gè)基類的成員,重寫了的函數(shù)同樣默認(rèn)是open的勒叠,如果想改變這一行為兜挨,可以顯示地將重寫的成員標(biāo)注為final

抽象類

我們可以將一個(gè)類聲明為abstract眯分,這種類不能被實(shí)例化拌汇,一個(gè)從抽象類通常包含一些沒有實(shí)現(xiàn)并且必須在子類重寫的抽象成員:

  • 抽象類中的抽象函數(shù):沒有函數(shù)體就默認(rèn)是abstract的,不一定要加上關(guān)鍵字弊决,其訪問性始終是open的噪舀。
  • 抽象類中的非抽象函數(shù):默認(rèn)是final的魁淳,如果需要重寫,那么需要加上open修飾符与倡。

小結(jié)

open界逛、finalabstract這三個(gè)訪問修飾符都 只適用于類,不能用在接口 當(dāng)中:

  • open:用于聲明一個(gè)類可以被繼承纺座,或者方法可以被子類重寫息拜。
  • final:不允許類被繼承,或者不允許方法被重寫净响。
  • abstract:聲明抽象類少欺,或者抽象類中的抽象方法。

當(dāng)我們需要重寫方法時(shí)馋贤,必須加上override修飾符赞别。

2.3 可見性修飾符

Kotlin的可見性修飾符包括以下四種:

修飾符 類成員 頂層聲明
public 所有地方可見 所有地方可見
internal 模塊中可見 模塊中可見
protected 子類中可見 ---
private 類中可見 文件中可見

JavaKotlin在可見性上的區(qū)別包括以下幾點(diǎn):

  • Java中默認(rèn)的可見性是包私有的,而在Kotlin中掸掸,默認(rèn)的可見性是public的氯庆。Kotlininternal作為包可見的替代方案,它表示“只在模塊內(nèi)部可見”扰付。
  • Kotlin允許在頂層聲明中使用private可見性堤撵,包括類、函數(shù)和屬性羽莺,這些聲明就只在聲明它們的文件中可見实昨,這是隱藏子系統(tǒng)實(shí)現(xiàn)細(xì)節(jié)的非常有用的方式。
  • 類的擴(kuò)展函數(shù)不能訪問它的privateprotected成員盐固。
  • Kotlin中荒给,一個(gè)外部類不能看到其內(nèi)部類中的private成員。

2.4 內(nèi)部類和嵌套類

Kotlin中刁卜,如果我們像Java一樣志电,在一個(gè)類的內(nèi)部定義一個(gè)類,那么它并不是一個(gè) 內(nèi)部類蛔趴,而是 嵌套類挑辆,區(qū)別在于嵌套類不會(huì)持有外部類的引用,也就是說它實(shí)際上是一個(gè)靜態(tài)內(nèi)部類:

嵌套類

如果要把它嵌套類變成一個(gè) 內(nèi)部類 來持有一個(gè)外部類的引用的話需要使用inner修飾符孝情,并且訪問外部類時(shí)鱼蝉,需要使用this@{外部類名}的方式。

內(nèi)部類

2.5 密封類:定義受限的類繼承結(jié)構(gòu)

之前在介紹when表達(dá)式的時(shí)候箫荡,我們用了一個(gè)表達(dá)式的例子魁亦,NumSum繼承于基類Expr,分別表達(dá)數(shù)字和兩個(gè)表達(dá)式之和羔挡,而對(duì)于不屬于Expr的子類洁奈,我們需要提供額外的else操作符间唉。


假如我們給Expr添加了一個(gè)新的子類,編譯器并不能發(fā)現(xiàn)有地方改變了睬魂。如果忘記了添加一個(gè)新分支终吼,就會(huì)選擇默認(rèn)的選項(xiàng),這有可能導(dǎo)致潛在的bug氯哮。

Kotlin為這個(gè)問題提供了一個(gè)解決方案:sealed類际跪。為父類添加一個(gè)sealed修飾符,對(duì)可能創(chuàng)建的子類做出嚴(yán)格的限制喉钢,所有的直接子類必須嵌套在父類中姆打。之前的例子修改如下:


這時(shí)候,我們?cè)?code>when表達(dá)式中已經(jīng)處理了所有Expr的子類肠虽,就不再需要提供默認(rèn)的分支幔戏,假如這時(shí)候我們給Expr添加一個(gè)新的子類Multi,但是不修改when中的邏輯税课,那么就會(huì)導(dǎo)致編譯失斚醒印:

提示的信息為:

在這種情況下,Expr類有一個(gè)只能在類內(nèi)部調(diào)用的private構(gòu)造方法韩玩,你也不能聲明一個(gè)sealed接口垒玲,因?yàn)槿绻@樣做,Kotlin編譯器不能保證任何人都不能在Java代碼中實(shí)現(xiàn)這個(gè)接口找颓。

三合愈、構(gòu)造方法

Java中,一個(gè)類可以聲明一個(gè)或多個(gè)構(gòu)造方法击狮,Kotlin則將構(gòu)造方法分為兩類:

  • 主構(gòu)造方法:主要而簡潔的初始化類的方法佛析,并且在 類體外部聲明
  • 從構(gòu)造方法:在 類體內(nèi)部聲明彪蓬。

3.1 初始化類:主構(gòu)造方法和初始化語句塊

假設(shè)寸莫,我們需要定義一個(gè)包含只讀nickname屬性的User類,最簡單的方式為:


上面這段被括號(hào)圍起來的語句塊就叫做 主構(gòu)造方法档冬,它有兩個(gè)目的:

  • 表明構(gòu)造方法的參數(shù)膘茎。
  • 定義使用這些參數(shù)初始化的屬性,也就是nikename捣郊。

用于完成上面這兩個(gè)功能的最明確的代碼如下所示:


  • constructor:用來開始一個(gè)主構(gòu)造方法和從構(gòu)造方法的聲明辽狈。
  • init:引入一個(gè)初始化塊語句慈参,這種語句塊包含了在類被創(chuàng)建時(shí)執(zhí)行的代碼呛牲,并會(huì)與主構(gòu)造方法一起使用,因?yàn)橹鳂?gòu)造方法有語法限制驮配,這就是為什么要使用初始化語句塊的原因娘扩。

在上面的例子中有幾個(gè)可以簡化的點(diǎn):

  • 放在初始化語句塊的語句可以和nikename的聲明結(jié)合着茸,因此可以去掉init語句。
  • 如果主構(gòu)造方法沒有注解或可見性修飾符琐旁,可以取消constructor關(guān)鍵字涮阔。
  • 如果屬性用相應(yīng)的構(gòu)造方法參數(shù)來初始化,代碼可以通過把val關(guān)鍵字加在參數(shù)前的方式來進(jìn)行簡化灰殴。

經(jīng)過了以上三點(diǎn)敬特,就會(huì)得到最前面簡化后的結(jié)果。

對(duì)于構(gòu)造方法牺陶,也可以采用之前在 Kotlin 知識(shí)梳理(2) - 函數(shù)的定義與調(diào)用 中介紹的 命名參數(shù)默認(rèn)參數(shù)值 的技巧伟阔,如果所有的構(gòu)造方法都有默認(rèn)值,編譯器會(huì)生成一個(gè)額外的不帶參數(shù)的構(gòu)造方法來使用所有的默認(rèn)值掰伸。

Java中皱炉,如果父類定義了一個(gè)構(gòu)造方法,那么在子類的構(gòu)造方法中狮鸭,必須要通過super方法初始化父類合搅,例如:

//父類。
public class User {

    private String nikeName;

    User(String nikeName) {
        this.nikeName = nikeName;
    }
}
//子類歧蕉。
public class TwitterUser extends User {

    public TwitterUser(String nikeName) {
       super(nikeName);
    }
}

而在Kotlin中灾部,可以通過在基類列表的父類引用中提供父類構(gòu)造方法參數(shù)的方式來做到這一點(diǎn):


假如一個(gè)類沒有聲明任何的構(gòu)造方法,將會(huì)生成一個(gè)不做任何事的默認(rèn)構(gòu)造方法廊谓,如果有子類繼承了它梳猪,那么必須顯示地調(diào)用父類的構(gòu)造方法,即使它沒有任何的參數(shù)蒸痹。
如果想要確保你的類不被其它代碼實(shí)例化春弥,必須把構(gòu)造方法標(biāo)記為private,我們對(duì)上面的例子進(jìn)行修改:


報(bào)錯(cuò)的原因?yàn)椋?/p>


在大多數(shù)真實(shí)的場(chǎng)景中叠荠,類的構(gòu)造方法是非常簡明的:它要么沒有參數(shù)或者直接與參數(shù)對(duì)應(yīng)的屬性關(guān)聯(lián)匿沛,這就是為了Kotlin有為主構(gòu)造方法設(shè)計(jì)的簡潔的語法。

3.2 用不同的方式來初始化父類

大多數(shù)在Java中需要重載構(gòu)造方法的場(chǎng)景都被Kotlin支持命名參數(shù)和參數(shù)默認(rèn)值的語法所覆蓋了榛鼎。

定義從構(gòu)造方法:constructor

而當(dāng)我們需要擴(kuò)展一個(gè)框架來提供多個(gè)構(gòu)造方法逃呼,以便于通過不同的方式來初始化類的時(shí)候,就會(huì)需要用到從構(gòu)造方法者娱,從構(gòu)造方式使用constructor方法引出抡笼,例如下面的代碼:


運(yùn)行結(jié)果為:

子類調(diào)用父類的從構(gòu)造方法:super

如果想要擴(kuò)展這個(gè)類,可以聲明同樣的構(gòu)造方法黄鳍,并使用super關(guān)鍵字調(diào)用對(duì)應(yīng)的父類構(gòu)造方法:


運(yùn)行結(jié)果為:

子類調(diào)用自己的另一個(gè)構(gòu)造方法:this

如果想要從一個(gè)構(gòu)造方法中推姻,調(diào)用你自己的類的另一個(gè)構(gòu)造方法,那么可以使用this關(guān)鍵字:


運(yùn)行結(jié)果為:

需要注意:如果類沒有主構(gòu)造方法框沟,那么每個(gè)從構(gòu)造方法必須初始化基類(通過super關(guān)鍵字)或者委托給另一個(gè)這樣做了的構(gòu)造方法(通過this關(guān)鍵字)藏古,也就是說增炭,每個(gè)從構(gòu)造方法必須以一個(gè)朝外的箭頭開始,并且結(jié)束于任意一個(gè)基類構(gòu)造方法拧晕,就像上面例子中Button的帶有兩個(gè)參數(shù)的從構(gòu)造方法所做的那樣隙姿。

3.3 實(shí)現(xiàn)在接口中聲明的屬性

Kotlin中,接口可以包含抽象屬性的聲明:


但是接口并沒有說明這個(gè)值應(yīng)該存儲(chǔ)到一個(gè)支持字段還是通過getter來獲取厂捞,接口本身并不包含任何狀態(tài)输玷,因此只有實(shí)現(xiàn)這個(gè)接口的類在需要的時(shí)候會(huì)存儲(chǔ)這個(gè)值。

下面是三個(gè)例子:


  • PrivateUser:直接在主構(gòu)造方法中聲明了這個(gè)屬性靡馁,這個(gè)屬性實(shí)現(xiàn)了來自于User的抽象屬性饲嗽,所以要標(biāo)記為override
  • SubscribingUser:通過一個(gè)自定義的getter實(shí)現(xiàn)奈嘿,這個(gè)屬性沒有一個(gè)支持字段來存儲(chǔ)它的值貌虾,它只有一個(gè)getter在每次調(diào)用時(shí)從email中得到昵稱。
  • FacebookUser:在初始化時(shí)裙犹,將nickname屬性與值關(guān)聯(lián)尽狠。

接口除了可以聲明抽象屬性外,還可以包含具有gettersetter的屬性叶圃,只要它們沒有引用一個(gè)支持字段(支持字段需要在接口中存儲(chǔ)狀態(tài)袄膏,而這是不允許的):

  • email:必須在子類中重寫。
  • nickname:有一個(gè)自定義的getter掺冠,可以被子類繼承沉馆。

運(yùn)行結(jié)果為:


4.4 通過 getter 和 setter 訪問支持字段

現(xiàn)在,我們已經(jīng)學(xué)習(xí)了兩種屬性的用法:

  • 存儲(chǔ)值的屬性
  • 具有自定義訪問器在每次訪問時(shí)計(jì)算值的屬性

現(xiàn)在德崭,我們結(jié)合以上兩種斥黑,來實(shí)現(xiàn)一個(gè)既可以存儲(chǔ)值,又可以在值被訪問和修改時(shí)提供額外邏輯的屬性:


運(yùn)行結(jié)果為:

上面的address就是 有支持字段的屬性眉厨,它和 沒有支持字段的屬性 的區(qū)別在于:

  • 如果顯示地引用或者使用默認(rèn)的訪問器實(shí)現(xiàn)锌奴,編譯器會(huì)為屬性生成支持字段。
  • 如果你提供了一個(gè)自定義的訪問器實(shí)現(xiàn)并且沒有使用field憾股,支持字段就不會(huì)被呈現(xiàn)出來鹿蜀。

4.5 修改訪問器的可見性

訪問器的可見性默認(rèn)與屬性的可見性相同,但是如果需要可以通過在getset關(guān)鍵字前放置可見性修飾符的方式來修改它服球,例如在下面的例子中茴恰,我們將setter的可見性修改為private


運(yùn)行結(jié)果為:


更多文章,歡迎訪問我的 Android 知識(shí)梳理系列:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末斩熊,一起剝皮案震驚了整個(gè)濱河市往枣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖婉商,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異渣叛,居然都是意外死亡丈秩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門淳衙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蘑秽,“玉大人,你說我怎么就攤上這事箫攀〕ι” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵靴跛,是天一觀的道長缀雳。 經(jīng)常有香客問我,道長梢睛,這世上最難降的妖魔是什么肥印? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮绝葡,結(jié)果婚禮上深碱,老公的妹妹穿的比我還像新娘。我一直安慰自己藏畅,他們只是感情好敷硅,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著愉阎,像睡著了一般绞蹦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上榜旦,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天坦辟,我揣著相機(jī)與錄音,去河邊找鬼章办。 笑死锉走,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的藕届。 我是一名探鬼主播挪蹭,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼休偶!你這毒婦竟也來了梁厉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎词顾,沒想到半個(gè)月后八秃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肉盹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年昔驱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片上忍。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡骤肛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出窍蓝,到底是詐尸還是另有隱情腋颠,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布吓笙,位于F島的核電站淑玫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏面睛。R本人自食惡果不足惜混移,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望侮穿。 院中可真熱鬧歌径,春花似錦、人聲如沸亲茅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽克锣。三九已至茵肃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間袭祟,已是汗流浹背验残。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留巾乳,地道東北人您没。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像胆绊,于是被迫代替她去往敵國和親氨鹏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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