[譯] Kotlin中關(guān)于Companion Object的那些事

compaion_object.png

翻譯說(shuō)明:

原標(biāo)題: A few facts about Companion objects

原文地址: https://blog.kotlin-academy.com/a-few-facts-about-companion-objects-37e18429b725](https://blog.kotlin-academy.com/a-few-facts-about-companion-objects-37e18429b725)

原文作者: David Blanc

Kotlin給Java開(kāi)發(fā)者帶來(lái)最大改變之一就是廢棄了static修飾符庞萍。與Java不同的是在Kotlin的類中不允許你聲明靜態(tài)成員或方法。相反,你必須向類中添加Companion對(duì)象來(lái)包裝這些靜態(tài)引用: 差異看起來(lái)似乎很小遵绰,但是它有一些明顯的不同公黑。

image

首先征冷,companion伴生對(duì)象是個(gè)實(shí)際對(duì)象的單例實(shí)例澄耍。你實(shí)際上可以在你的類中聲明一個(gè)單例浊伙,并且可以像companion伴生對(duì)象那樣去使用它样漆。這就意味著在實(shí)際開(kāi)發(fā)中为障,你不僅僅只能使用一個(gè)靜態(tài)對(duì)象來(lái)管理你所有的靜態(tài)屬性! companion這個(gè)關(guān)鍵字實(shí)際上只是一個(gè)快捷方式,允許你通過(guò)類名訪問(wèn)該對(duì)象的內(nèi)容(如果伴生對(duì)象存在一個(gè)特定的類中,并且只是用到其中的方法或?qū)傩悦Q鳍怨,那么伴生對(duì)象的類名可以省略不寫(xiě))呻右。就編譯而言,下面的testCompanion()方法中的三行都是有效的語(yǔ)句鞋喇。

class TopLevelClass {

    companion object {
        fun doSomeStuff() {
            ...
        }
    }

    object FakeCompanion {
        fun doOtherStuff() {
            ...
        }
    }
}

fun testCompanion() {
    TopLevelClass.doSomeStuff()
    TopLevelClass.Companion.doSomeStuff()
    TopLevelClass.FakeCompanion.doOtherStuff()
}

為了兼容的公平性声滥,companion關(guān)鍵字還提供了更多選項(xiàng),尤其是與Java互操作性相關(guān)選項(xiàng)确徙。果您嘗試在Java類中編寫(xiě)相同的測(cè)試代碼醒串,調(diào)用方式可能會(huì)略有不同:

public void testCompanion() {
    TopLevelClass.Companion.doSomeStuff();
    TopLevelClass.FakeCompanion.INSTANCE.doOtherStuff();
}

區(qū)別在于: Companion作為Java代碼中靜態(tài)成員開(kāi)放(實(shí)際上它是一個(gè)對(duì)象實(shí)例,但是由于它的名稱是以大寫(xiě)的C開(kāi)頭鄙皇,所以有點(diǎn)存在誤導(dǎo)性)芜赌,而FakeCompanion引用了我們的第二個(gè)單例對(duì)象的類名。在第二個(gè)方法調(diào)用中伴逸,我們需要使用它的INSTANCE屬性來(lái)實(shí)際訪問(wèn)Java中的實(shí)例(你可以打開(kāi)IntelliJ IDEA或AndroidStudio中的"Show Kotlin Bytecode"菜單欄缠沈,并點(diǎn)擊里面"Decompile"按鈕來(lái)查看反編譯后對(duì)應(yīng)的Java代碼)

在這兩種情況下(不管是Kotlin還是Java),使用伴生對(duì)象Companion類比FakeCompanion類那種調(diào)用語(yǔ)法更加簡(jiǎn)短错蝴。此外洲愤,由于Kotlin提供一些注解,可以讓編譯器生成一些簡(jiǎn)短的調(diào)用方式顷锰,以便于在Java代碼中依然可以像在Kotlin中那樣簡(jiǎn)短形式調(diào)用柬赐。

@JvmField注解,例如告訴編譯器不要生成gettersetter,而是生成Java中成員官紫。在伴生對(duì)象的作用域內(nèi)使用該注解標(biāo)記某個(gè)成員肛宋,它產(chǎn)生的副作用是標(biāo)記這個(gè)成員不在伴生對(duì)象內(nèi)部作用域,而是作為一個(gè)Java最外層類的靜態(tài)成員存在束世。從Kotlin的角度來(lái)看酝陈,這沒(méi)有什么太大區(qū)別,但是如果你看一下反編譯的字節(jié)代碼毁涉,你就會(huì)注意到伴生對(duì)象以及他的成員都聲明和最外層類的靜態(tài)成員處于同一級(jí)別沉帮。

另一個(gè)有用的注解 @JvmStatic.這個(gè)注解允許你調(diào)用伴生對(duì)象中聲明的方法就像是調(diào)用外層的類的靜態(tài)方法一樣。但是需要注意的是:在這種情況下贫堰,方法不會(huì)和上面的成員一樣移出伴生對(duì)象的內(nèi)部作用域穆壕。因?yàn)榫幾g器只是向外層類中添加一個(gè)額外的靜態(tài)方法,然后在該方法內(nèi)部又委托給伴生對(duì)象其屏。

一起來(lái)看一下這個(gè)簡(jiǎn)單的Kotlin類例子:

class MyClass {
    companion object {
        @JvmStatic
        fun aStaticFunction() {}
    }
}

這是相應(yīng)編譯后的Java簡(jiǎn)化版代碼:

public class MyClass {
    public static final MyClass.Companion Companion = new MyClass.Companion();
    fun aStaticFunction() {//外層類中添加一個(gè)額外的靜態(tài)方法
        Companion.aStaticFunction();//方法內(nèi)部又委托給伴生對(duì)象的aStaticFunction方法
    }
    public static final class Companion {
         public final void aStaticFunction() {}
    }
}

這里存在一個(gè)非常細(xì)微的差別粱檀,但在某些特殊的情況下可能會(huì)出問(wèn)題。例如漫玄,考慮一下Dagger中的module(模塊)茄蚯。當(dāng)定義一個(gè)Dagger模塊時(shí)压彭,你可以使用靜態(tài)方法去提升性能,但是如果你選擇這樣做渗常,如果您的模塊包含靜態(tài)方法以外的任何內(nèi)容壮不,則編譯將失敗。由于Kotlin在類中既包含靜態(tài)方法皱碘,也保留了靜態(tài)伴生對(duì)象询一,因此無(wú)法以這種方式編寫(xiě)僅僅包含靜態(tài)方法的Kotlin類。

但是不要那么快放棄! 這并不意味著你不能這樣做癌椿,只是它需要一個(gè)稍微不同的處理方式:在這種特殊的情況下健蕊,你可以使用Kotlin單例(使用object對(duì)象表達(dá)式而不是class類)替換含有靜態(tài)方法的Java類并在每個(gè)方法上使用@JvmStatic注解。如下例所示:在這種情況下踢俄,生成的字節(jié)代碼不再顯示任何伴生對(duì)象缩功,靜態(tài)方法會(huì)附加到類中。

@Module
object MyModule {

    @Provides
    @Singleton
    @JvmStatic
    fun provideSomething(anObject: MyObject): MyInterface {
        return myObject
    }
}

這又讓你再一次明白了伴生對(duì)象僅僅是單例對(duì)象的一個(gè)特例都办。但它至少表明與許多人的認(rèn)知是相反的嫡锌,你不一定需要一個(gè)伴生對(duì)象來(lái)維護(hù)靜態(tài)方法或靜態(tài)變量。你甚至根本不需要一個(gè)對(duì)象來(lái)維護(hù)琳钉,只要考慮頂層函數(shù)或常量:它們將作為靜態(tài)成員被包含在一個(gè)自動(dòng)生成的類中(默認(rèn)情況下势木,例如MyFileKt會(huì)作為MyFile.kt文件生成的類名,一般生成類名以Kt為后綴結(jié)尾)

我們有點(diǎn)偏離這篇文章的主題了歌懒,所以讓我們繼續(xù)回到伴生對(duì)象上來(lái)±沧溃現(xiàn)在你已經(jīng)了解了伴生對(duì)象實(shí)質(zhì)就是對(duì)象,也應(yīng)該意識(shí)到它開(kāi)放了更多的可能性及皂,例如繼承和多態(tài)甫男。

這意味著你的伴生對(duì)象并不是沒(méi)有類型或父類的匿名對(duì)象。它不僅可以擁有父類躲庄,而且它甚至可以實(shí)現(xiàn)接口以及含有對(duì)象名。它不需要被稱為companion钾虐。這就是為什么你可以這樣寫(xiě)一個(gè)Parcelable類:

class ParcelableClass() : Parcelable {

    constructor(parcel: Parcel) : this()

    override fun writeToParcel(parcel: Parcel, flags: Int) {}

    override fun describeContents() = 0

    companion object CREATOR : Parcelable.Creator<ParcelableClass> {
        override fun createFromParcel(parcel: Parcel): ParcelableClass = ParcelableClass(parcel)

        override fun newArray(size: Int): Array<ParcelableClass?> = arrayOfNulls(size)
    }
}

這里, 伴生對(duì)象名為CREATOR噪窘,它實(shí)現(xiàn)了Android中的Parcelable.Creator接口,允許遵守Parcelable約定效扫,同時(shí)保持比使用@JvmField注釋在伴隨對(duì)象內(nèi)添加Creator對(duì)象更直觀倔监。Kotlin中引入了@Parcelize注解,以便于可以獲得所有樣板代碼菌仁,但是在這不是重點(diǎn)...

為了使它變得更簡(jiǎn)潔浩习,如果你的伴生對(duì)象可以實(shí)現(xiàn)接口,它甚至可以使用Kotlin中的代理來(lái)執(zhí)行此操作:

class MyObject {
    companion object : Runnable by MyRunnable()
}

這將允許您同時(shí)向多個(gè)對(duì)象中添加靜態(tài)方法济丘!請(qǐng)注意谱秽,伴生對(duì)象在這種情況下甚至不需要作用域體洽蛀,因?yàn)樗怯纱硖峁┑摹?/p>

最后但同樣重要的是,你可以為伴生對(duì)象定義擴(kuò)展函數(shù)! 這就意味著你可以在現(xiàn)有的類中添加靜態(tài)方法或靜態(tài)屬性疟赊,如下例所示:

class MyObject {

    companion object

    fun useCompanionExtension() {
        someExtension()
    }

}

fun MyObject.Companion.someExtension() {}//定義擴(kuò)展函數(shù)

這樣做有什么意義郊供?我真的不知道。雖然Marcin Moskala建議使用此操作將靜態(tài)工廠方法以Companion的擴(kuò)展函數(shù)的形式添加到類中近哟。

總而言之驮审,伴生對(duì)象不僅僅是為了給缺少static修飾符的使用場(chǎng)景提供解決方案:

  • 它們是真正的Kotlin對(duì)象,包括名稱和類型吉执,以及一些額外的功能疯淫。
  • 他們甚至可以不用于僅僅為了提供靜態(tài)成員或方法場(chǎng)景〈撩担可以有更多其他選擇熙掺,比如他們可以用作單例對(duì)象或替代頂層函數(shù)的功能。

與大多數(shù)場(chǎng)景一樣量九,Kotlin意味著在你設(shè)計(jì)過(guò)程需要有一點(diǎn)點(diǎn)轉(zhuǎn)變适掰,但與Java相比,它并沒(méi)有真正限制你的選擇荠列。如果有的話类浪,也會(huì)通過(guò)提供一些新的、更簡(jiǎn)潔的方式讓你去使用它肌似。

<div align="center"><img src="https://user-gold-cdn.xitu.io/2018/5/14/1635c3fb0ba21ec1?w=430&h=430&f=jpeg&s=39536" width="200" height="200"></div>

歡迎關(guān)注Kotlin開(kāi)發(fā)者聯(lián)盟费就,這里有最新Kotlin技術(shù)文章,每周會(huì)不定期翻譯一篇Kotlin國(guó)外技術(shù)文章川队。如果你也喜歡Kotlin力细,歡迎加入我們~~~

Kotlin系列文章,歡迎查看:

Kotlin邂逅設(shè)計(jì)模式系列:

數(shù)據(jù)結(jié)構(gòu)與算法系列:

Kotlin 原創(chuàng)系列:

Effective Kotlin翻譯系列

翻譯系列:

實(shí)戰(zhàn)系列:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市啄糙,隨后出現(xiàn)的幾起案子笛臣,更是在濱河造成了極大的恐慌艺普,老刑警劉巖纯露,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異浪听,居然都是意外死亡燕雁,警方通過(guò)查閱死者的電腦和手機(jī)诞丽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)鲸拥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人率拒,你說(shuō)我怎么就攤上這事崩泡。” “怎么了猬膨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵角撞,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我勃痴,道長(zhǎng)谒所,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任沛申,我火速辦了婚禮劣领,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘铁材。我一直安慰自己尖淘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布著觉。 她就那樣靜靜地躺著村生,像睡著了一般。 火紅的嫁衣襯著肌膚如雪饼丘。 梳的紋絲不亂的頭發(fā)上趁桃,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音肄鸽,去河邊找鬼卫病。 笑死,一個(gè)胖子當(dāng)著我的面吹牛典徘,可吹牛的內(nèi)容都是我干的蟀苛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼逮诲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼帜平!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起汛骂,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤罕模,失蹤者是張志新(化名)和其女友劉穎评腺,沒(méi)想到半個(gè)月后帘瞭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蒿讥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年蝶念,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抛腕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡媒殉,死狀恐怖担敌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情廷蓉,我是刑警寧澤全封,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站桃犬,受9級(jí)特大地震影響刹悴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜攒暇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一土匀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧形用,春花似錦就轧、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至每币,卻和暖如春携丁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背兰怠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工梦鉴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人揭保。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓肥橙,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親秸侣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子存筏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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