Kotlin 類(lèi)募谎、對(duì)象和接口(二)——聲明一個(gè)帶非默認(rèn)構(gòu)造方法或?qū)傩缘念?lèi)

Kotlin 類(lèi)拜银、對(duì)象和接口(一)——定義類(lèi)繼承結(jié)構(gòu)

在 Java 中一個(gè)類(lèi)可以聲明一個(gè)或多個(gè)構(gòu)造方法殊鞭,Kotlin 也是類(lèi)似的,只是做出了一點(diǎn)修改:區(qū)分了構(gòu)造方法 (通常是主要而間接的初始化類(lèi)的方法尼桶,并且在類(lèi)體外部聲明) 和構(gòu)造方法(在類(lèi)體內(nèi)部聲明)操灿。同樣也允許在初始化語(yǔ)句塊中添加額外的初始化邏輯。

初始化類(lèi):主構(gòu)造方法和初始化語(yǔ)句塊

class User(val nickname: String)

通常來(lái)講泵督,類(lèi)的所有聲明都在花括號(hào)中趾盐,但上面這個(gè)類(lèi)沒(méi)有花括號(hào)而是只包含了聲明在括號(hào)中。這段被括號(hào)圍起來(lái)的語(yǔ)句塊就叫做主構(gòu)造方法。它主要有兩個(gè)目的:表明構(gòu)造方法的參數(shù)救鲤,以及定義使用那些參數(shù)初始化的屬性久窟。它的工作原理以及完成同樣事情的最明確的代碼如下:

// 帶一個(gè)參數(shù)的主構(gòu)造方法
class User constructor(_nickname: String) {
    val nickname: String
    
    // 初始化語(yǔ)句塊
    init {
        nickname = _nickname
    }
}

在這個(gè)例子中,constructor 關(guān)鍵字用來(lái)開(kāi)始一個(gè)主構(gòu)造方法或從構(gòu)造方法的聲明本缠。init 關(guān)鍵字用來(lái)引入一個(gè)初始化語(yǔ)句塊斥扛。這種語(yǔ)句塊包含了在類(lèi)被創(chuàng)建時(shí)執(zhí)行的代碼,并會(huì)與主構(gòu)造方法一起使用丹锹。因?yàn)橹鳂?gòu)造方法有語(yǔ)法限制稀颁,不能包含初始化代碼,這就是為什么要使用初始化語(yǔ)句塊的原因楣黍。也可以在一個(gè)類(lèi)中聲明多個(gè)初始化語(yǔ)句塊匾灶。
構(gòu)造方法參數(shù) _nickname 中的下劃線用來(lái)區(qū)分屬性的名稱(chēng)和構(gòu)造方法參數(shù)的名字。另一個(gè)可選方案是使用同樣的名字租漂,通過(guò) this 來(lái)消除歧義阶女,就像 Java 中的常用做法一樣:this.nickname = nickname。
在這個(gè)例子中哩治,不需要把初始化代碼放在初始化語(yǔ)句塊中秃踩,因?yàn)樗梢耘c nickname 屬性的聲明結(jié)合。如果主構(gòu)造方法沒(méi)有注解或可見(jiàn)性修飾符锚扎,同樣可以去掉 constructor 關(guān)鍵字隔盛,代碼如下:

// 帶一個(gè)參數(shù)的主構(gòu)造方法
class User(_nickname: String) {
    // 用參數(shù)來(lái)初始化屬性
    val nickname = _nickname
}

這就是聲明同樣的類(lèi)的另一種方法丽已。墻面兩個(gè)例子在類(lèi)體中使用 val 關(guān)鍵字聲明了屬性,如果屬性用相應(yīng)的構(gòu)造方法參數(shù)來(lái)初始化炸庞,代碼可以通過(guò)把 val 關(guān)鍵字加在參數(shù)前的方式來(lái)進(jìn)行簡(jiǎn)化惯疙〈涿悖可以替換類(lèi)中的屬性定義:

// "val" 意味著相應(yīng)的屬性會(huì)用構(gòu)造方法的參數(shù)來(lái)初始化
class User(val nickname: String)

所有 User 類(lèi)的聲明都是等價(jià)的,但是最后一個(gè)使用了最簡(jiǎn)明的語(yǔ)法霉颠。
可以像函數(shù)一樣為構(gòu)造方法參數(shù)聲明一個(gè)默認(rèn)值:

// 為構(gòu)造方法參數(shù)提供一個(gè)默認(rèn)值
class User(val nickname: String, val isSubscribed: Boolean = true)

創(chuàng)建一個(gè)類(lèi)的實(shí)例对碌,只需要直接調(diào)用構(gòu)造方法,不需要 new 關(guān)鍵字:

// 為 isSubscribed 參數(shù)使用默認(rèn)值 “true”
val alice = User("Alice")
// 可以按照聲明順序?qū)懨魉械膮?shù)
val bob = User("Bob", false)
// 可以顯式的為某些構(gòu)造方法參數(shù)表明名稱(chēng)
val carol = User("Carol", isSubscribed = false)

注意 如果所有的構(gòu)造方法參數(shù)都有默認(rèn)值蒿偎,編譯器會(huì)生成一個(gè)額外的不帶參數(shù)的構(gòu)造方法來(lái)使用所有的默認(rèn)值朽们。這可以讓 Kotlin 使用庫(kù)時(shí)變得更簡(jiǎn)單,因?yàn)榭梢酝ㄟ^(guò)無(wú)參構(gòu)造方法來(lái)實(shí)例化類(lèi)诉位。

如果類(lèi)具有一個(gè)父類(lèi)骑脱,主構(gòu)造方法同樣需要初始化父類(lèi)〔钥罚可以通過(guò)在基類(lèi)列表的父類(lèi)引用中提供父類(lèi)構(gòu)造方法參數(shù)的方式來(lái)做到這一點(diǎn):

open class User(val nickname: String) {...}
class TwitterUser(nickname: String) : User(nickname) {...}

如果沒(méi)有給一個(gè)類(lèi)聲明任何的構(gòu)造方法叁丧,將會(huì)生成一個(gè)不做任何事情的默認(rèn)構(gòu)造方法:

// 將會(huì)生成一個(gè)不帶任何參數(shù)的默認(rèn)構(gòu)造方法
open class Button

如果繼承了 Button 類(lèi)并且沒(méi)有提供任何的構(gòu)造方法,必須顯式的調(diào)用父類(lèi)的構(gòu)造方法,即使它沒(méi)有任何的參數(shù):

class RadioButton: Button()

注意與接口的區(qū)別:接口沒(méi)有構(gòu)造方法拥娄,所以在實(shí)現(xiàn)一個(gè)接口的時(shí)候蚊锹,不需要在父類(lèi)型列表中它的名稱(chēng)后面加上括號(hào)。
如果想要確保類(lèi)不被其他代碼實(shí)例化稚瘾,必須把構(gòu)造方法標(biāo)記為 private牡昆。把主構(gòu)造方法標(biāo)記為 private 代碼如下:

// 這個(gè)類(lèi)有一個(gè) private 構(gòu)造方法
class Secretive private constructor() {}

因?yàn)?Secretive 類(lèi)只有一個(gè) private 的構(gòu)造方法,這個(gè)類(lèi)外部的代碼不能實(shí)例化它孟抗。

private 構(gòu)造方法的替代方案
在 Java 中迁杨,可以通過(guò)使用 private 構(gòu)造方法禁止實(shí)例化這個(gè)類(lèi)來(lái)表示一個(gè)更通用的意思:這個(gè)類(lèi)是一個(gè)靜態(tài)實(shí)用工具成員的容器或者是單例的。Kotlin 針對(duì)這種目的具有內(nèi)建的語(yǔ)言級(jí)別的功能凄硼∏π可以使用頂層函數(shù)作為靜態(tài)實(shí)用工具,要想表示單例摊沉,可以使用對(duì)象聲明狐史。

在大多數(shù)真實(shí)的場(chǎng)景中,類(lèi)的構(gòu)造方法是非常簡(jiǎn)明的:它要么沒(méi)有參數(shù)或者直接把參數(shù)與對(duì)應(yīng)的屬性關(guān)聯(lián)说墨。這就是為什么 Kotlin 有為主構(gòu)造方法設(shè)計(jì)的簡(jiǎn)潔的語(yǔ)法:在大多數(shù)的情況下都能很好地工作骏全。


構(gòu)造方法:用不同的方式來(lái)初始化父類(lèi)

通常來(lái)講,使用多個(gè)構(gòu)造方法的類(lèi)在 Kotlin 代碼中不如在 Java 中常見(jiàn)尼斧。大多數(shù)在 Java 中需要重載構(gòu)造方法的場(chǎng)景都被 Kotlin 支持參數(shù)默認(rèn)值和參數(shù)命名的語(yǔ)法涵蓋了姜贡。
Tips 不要聲明多個(gè)從構(gòu)造方法來(lái)重載和提供參數(shù)的默認(rèn)值。取而代之的是棺棵,應(yīng)該直接標(biāo)明默認(rèn)值楼咳。
但是還是會(huì)有需要多個(gè)構(gòu)造方法的情景。最常見(jiàn)的一種就來(lái)自于當(dāng)需要擴(kuò)展一個(gè)框架來(lái)提供多個(gè)構(gòu)造方法烛恤,以便于通過(guò)不同的方式來(lái)初始化類(lèi)的時(shí)候母怜。如一個(gè)在 Java 中聲明的具有兩個(gè)構(gòu)造方法的類(lèi),Kotlin 中相似的聲明如下:

open class View {
    constructor(ctx: Context) {
        // some code
    }
    constructor(ctx: Context, attr: AttributeSet) {
        // some code
    }
}

這個(gè)類(lèi)沒(méi)有聲明一個(gè)主構(gòu)造方法(因?yàn)轭?lèi)頭部的類(lèi)名后面并沒(méi)有括號(hào))缚柏,但是它聲明了兩個(gè)從構(gòu)造方法苹熏。從構(gòu)造方法使用 constructor 關(guān)鍵字引出。只要需要們可以聲明任意多個(gè)從構(gòu)造方法币喧。
如果想擴(kuò)展這個(gè)類(lèi)轨域,可以聲明同樣的構(gòu)造方法:

class MyButton: View {
    constructor(ctx: Context) : super(ctx) {
        // ...
    }
    constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) {
        // ...
    }
}

這里定義了兩個(gè)構(gòu)造方法,他們都是用 super() 關(guān)鍵字調(diào)用了對(duì)應(yīng)的父類(lèi)構(gòu)造方法杀餐。就像在 Java 中一樣干发,也可以使用 this() 關(guān)鍵字,從一個(gè)構(gòu)造方法中調(diào)用自己類(lèi)中的另一個(gè)構(gòu)造方法怜浅,如下:

class MyButton: View {
    // 委托給這個(gè)類(lèi)的另一個(gè)構(gòu)造方法
    constructor(ctx: Context) : this(ctx, MY_STYLE) {
        // ...
    }
    constructor(ctx: Context, attr: AttributeSet) : super(ctx, attr) {
        // ...
    }
}

可以修改 MyButton 類(lèi)使得一個(gè)構(gòu)造方法委托給同一個(gè)類(lèi)的另一個(gè)構(gòu)造方法(使用 this)铐然,為參數(shù)傳入默認(rèn)值蔬崩。如果類(lèi)沒(méi)有主構(gòu)造方法,那么每個(gè)從構(gòu)造方法必須初始化基類(lèi)或者委托給另一個(gè)這樣做了的構(gòu)造方法搀暑,每個(gè)構(gòu)造方法必須以一個(gè)朝外的箭頭開(kāi)始并且結(jié)束于任意一個(gè)基類(lèi)構(gòu)造方法沥阳。


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

在 Kotlin 中,接口可以包含抽象屬性聲明自点。如下:

interface User {
    val nickname: String
}

這就意味著實(shí)現(xiàn) User 接口的類(lèi)需要提供一個(gè)取得 nickname 值的方式桐罕。接口并沒(méi)有說(shuō)明這個(gè)值應(yīng)該存儲(chǔ)到一個(gè)支持字段還是通過(guò) getter 來(lái)獲取。接口本身并不包含任何狀態(tài)桂敛,因此只是實(shí)現(xiàn)這個(gè)接口的類(lèi)在需要的情況下會(huì)存儲(chǔ)這個(gè)值功炮。

// 代碼清單 2.1     實(shí)現(xiàn)一個(gè)接口屬性
class PrivateUser(override val nickname: String) : User

class SubscriberingUser(val email: String) : User {
    override val nickname: String
    // 自定義 getter
        get() = email.substringBefore('@')
}

class FacebookUser(val accountId: Int) : User {
    override val nickname = getFacebookName(accountId)
}

對(duì)于 PrivateUser 來(lái)說(shuō),使用了簡(jiǎn)潔的語(yǔ)法直接在主構(gòu)造方法中聲明了一個(gè)屬性术唬。這個(gè)屬性實(shí)現(xiàn)了來(lái)自 User 的抽象屬性薪伏,所以將其標(biāo)記為 override。
對(duì)于 SubscribingUser 來(lái)說(shuō)粗仓,nickname 屬性通過(guò)一個(gè)自定義 getter 實(shí)現(xiàn)嫁怀,這個(gè)屬性沒(méi)有一個(gè)支持字段來(lái)存儲(chǔ)它的值,它只有一個(gè) getter 在每次調(diào)用時(shí)從 email 中得到昵稱(chēng)借浊。
對(duì)于 FacebookUser 來(lái)說(shuō)塘淑,在初始化時(shí)將 nickname 屬性與值關(guān)聯(lián)。使用了被認(rèn)為可以通過(guò)賬號(hào) ID 返回 Facebook 用戶(hù)名稱(chēng)的 getFacebookName 函數(shù)蚂斤。
除了抽象屬性聲明外存捺,接口還可以包含具有 getter 和 setter 的屬性,只要它們沒(méi)有應(yīng)用一個(gè)支持字段(支持字段需要在接口中存儲(chǔ)狀態(tài)曙蒸,而這是不允許的)捌治。


通過(guò) getter 或 setter 訪問(wèn)支持字段

前面介紹了屬性的兩種類(lèi)型:存儲(chǔ)值的屬性和具有自定義訪問(wèn)器在每次訪問(wèn)時(shí)計(jì)算值的屬性。若想要結(jié)合這兩種來(lái)實(shí)現(xiàn)一個(gè)既可以存儲(chǔ)值又可以在值被訪問(wèn)和修改時(shí)提供額外邏輯的屬性逸爵,就需要能夠從屬性的訪問(wèn)其中訪問(wèn)它的支持字段具滴。
假設(shè)想在任何對(duì)存儲(chǔ)在屬性中的數(shù)據(jù)進(jìn)行修改時(shí)輸出日志凹嘲,聲明了一個(gè)可變屬性并且在每次 setter 訪問(wèn)時(shí)執(zhí)行額外的代碼:

// 代碼清單 2.2     在 setter 中訪問(wèn)支持字段
class User(val name: String) {
    var address: String = "unspecified"
        set(value: String) {
            println("""
            Address was changed for $name:
            "$field" -> "$value".""".trimIndent())// 讀取支持字段的值
            // 更新支持字段的值
            field = value
        }
}

可以像平常一樣通過(guò)使用 user.address = "new value" 來(lái)修改一個(gè)屬性的值师倔,這其實(shí)在底層調(diào)用了 setter。
在 setter 的函數(shù)體中周蹭,使用了特殊的標(biāo)識(shí)符 field 來(lái)訪問(wèn)支持字段的值趋艘。在 getter 中,只能讀取值凶朗;在 setter 中瓷胧,既能讀取它也能修改它。
可以只重定義可變屬性的一個(gè)訪問(wèn)器棚愤。
訪問(wèn)屬性的方式不依賴(lài)于它是否有支持字段搓萧,如果顯式地引用或使用默認(rèn)的訪問(wèn)器實(shí)現(xiàn)杂数,編譯器會(huì)為屬性生成支持字段。如果提供了一個(gè)自定義的訪問(wèn)器實(shí)現(xiàn)并且沒(méi)有使用 field瘸洛,支持字段將不會(huì)被呈現(xiàn)出來(lái)揍移。


修改訪問(wèn)器的可見(jiàn)性

訪問(wèn)器的可見(jiàn)性默認(rèn)與屬性的可見(jiàn)性相同。但是如果需要反肋,可以通過(guò)在 get 和 set 關(guān)鍵字前放置可見(jiàn)性修飾符的方式來(lái)修改它那伐。

// 代碼清單 2.3     聲明一個(gè)具有 private setter 的屬性
class LengthCounter {
    var counter: Int = 0
    // 不能在類(lèi)外部修改這個(gè)屬性
        private set

    fun addWord(word: String) {
        counter += word.length
    }
}

這個(gè)類(lèi)用來(lái)計(jì)算單詞加在一起的總長(zhǎng)度。持有總長(zhǎng)度的屬性是 public 的石蔗,因?yàn)樗沁@個(gè)類(lèi)提供給客戶(hù)的 API 的一部分罕邀。但是需要確保它只能在類(lèi)中被修改,否則外部代碼有可能會(huì)修改它并存儲(chǔ)一個(gè)不正確的值养距。因此诉探,讓編譯器生成一個(gè)默認(rèn)可見(jiàn)性的 getter 方法,并且將 setter 的可見(jiàn)性修改為 private棍厌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末阵具,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子定铜,更是在濱河造成了極大的恐慌阳液,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件揣炕,死亡現(xiàn)場(chǎng)離奇詭異帘皿,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)畸陡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)鹰溜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人丁恭,你說(shuō)我怎么就攤上這事曹动。” “怎么了牲览?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵墓陈,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我第献,道長(zhǎng)贡必,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任庸毫,我火速辦了婚禮仔拟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘飒赃。我一直安慰自己利花,他們只是感情好科侈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著炒事,像睡著了一般兑徘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上羡洛,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天挂脑,我揣著相機(jī)與錄音,去河邊找鬼欲侮。 笑死崭闲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的威蕉。 我是一名探鬼主播刁俭,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼韧涨!你這毒婦竟也來(lái)了牍戚?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤虑粥,失蹤者是張志新(化名)和其女友劉穎如孝,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體娩贷,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡第晰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了彬祖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茁瘦。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖储笑,靈堂內(nèi)的尸體忽然破棺而出甜熔,到底是詐尸還是另有隱情,我是刑警寧澤突倍,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布腔稀,位于F島的核電站,受9級(jí)特大地震影響赘方,放射性物質(zhì)發(fā)生泄漏烧颖。R本人自食惡果不足惜弱左,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一窄陡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拆火,春花似錦跳夭、人聲如沸涂圆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)润歉。三九已至,卻和暖如春颈抚,著一層夾襖步出監(jiān)牢的瞬間踩衩,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工贩汉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驱富,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓匹舞,卻偏偏與公主長(zhǎng)得像褐鸥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子赐稽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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

  • 寫(xiě)在開(kāi)頭:本人打算開(kāi)始寫(xiě)一個(gè)Kotlin系列的教程姊舵,一是使自己記憶和理解的更加深刻晰绎,二是可以分享給同樣想學(xué)習(xí)Kot...
    胡奚冰閱讀 1,424評(píng)論 5 11
  • Kotlin的類(lèi)和接口與Java的類(lèi)和接口是有一定的區(qū)別的寒匙。Kotlin的接口是可以包含屬性聲明。Kotlin默認(rèn)...
    程自舟閱讀 10,338評(píng)論 0 11
  • 前言 人生苦多躏将,快來(lái) Kotlin 锄弱,快速學(xué)習(xí)Kotlin! 什么是Kotlin祸憋? Kotlin 是種靜態(tài)類(lèi)型編程...
    任半生囂狂閱讀 26,212評(píng)論 9 118
  • Kotlin 知識(shí)梳理系列文章 Kotlin 知識(shí)梳理(1) - Kotlin 基礎(chǔ)Kotlin 知識(shí)梳理(2) ...
    澤毛閱讀 4,021評(píng)論 1 10
  • 天氣好熱蚯窥,熱的嚇人掸鹅,這樣的天氣是會(huì)死人的!希望家里的奶奶照顧好自己拦赠!路上看到環(huán)衛(wèi)工人巍沙,還在干活,真是辛苦荷鼠,唉句携,領(lǐng)導(dǎo)...
    sdxl閱讀 96評(píng)論 0 1