翻譯說(shuō)明:
原標(biāo)題: A few facts about Companion objects
原文作者: David Blanc
Kotlin給Java開(kāi)發(fā)者帶來(lái)最大改變之一就是廢棄了static
修飾符庞萍。與Java不同的是在Kotlin的類中不允許你聲明靜態(tài)成員或方法。相反,你必須向類中添加Companion對(duì)象來(lái)包裝這些靜態(tài)引用: 差異看起來(lái)似乎很小遵绰,但是它有一些明顯的不同公黑。
首先征冷,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注解,例如告訴編譯器不要生成getter
和setter
,而是生成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)系列:
- 教你如何完全解析Kotlin中的類型系統(tǒng)
- 如何讓你的回調(diào)更具Kotlin風(fēng)味
- Jetbrains開(kāi)發(fā)者日見(jiàn)聞(三)之Kotlin1.3新特性(inline class篇)
- JetBrains開(kāi)發(fā)者日見(jiàn)聞(二)之Kotlin1.3的新特性(Contract契約與協(xié)程篇)
- JetBrains開(kāi)發(fā)者日見(jiàn)聞(一)之Kotlin/Native 嘗鮮篇
- 教你如何攻克Kotlin中泛型型變的難點(diǎn)(實(shí)踐篇)
- 教你如何攻克Kotlin中泛型型變的難點(diǎn)(下篇)
- 教你如何攻克Kotlin中泛型型變的難點(diǎn)(上篇)
- Kotlin的獨(dú)門(mén)秘籍Reified實(shí)化類型參數(shù)(下篇)
- 有關(guān)Kotlin屬性代理你需要知道的一切
- 淺談Kotlin中的Sequences源碼解析
- 淺談Kotlin中集合和函數(shù)式API完全解析-上篇
- 淺談Kotlin語(yǔ)法篇之lambda編譯成字節(jié)碼過(guò)程完全解析
- 淺談Kotlin語(yǔ)法篇之Lambda表達(dá)式完全解析
- 淺談Kotlin語(yǔ)法篇之?dāng)U展函數(shù)
- 淺談Kotlin語(yǔ)法篇之頂層函數(shù)固额、中綴調(diào)用眠蚂、解構(gòu)聲明
- 淺談Kotlin語(yǔ)法篇之如何讓函數(shù)更好地調(diào)用
- 淺談Kotlin語(yǔ)法篇之變量和常量
- 淺談Kotlin語(yǔ)法篇之基礎(chǔ)語(yǔ)法
Effective Kotlin翻譯系列
- [譯]Effective Kotlin系列之考慮使用原始類型的數(shù)組優(yōu)化性能(五)
- [譯]Effective Kotlin系列之使用Sequence來(lái)優(yōu)化集合的操作(四)
- [譯]Effective Kotlin系列之探索高階函數(shù)中inline修飾符(三)
- [譯]Effective Kotlin系列之遇到多個(gè)構(gòu)造器參數(shù)要考慮使用構(gòu)建器(二)
- [譯]Effective Kotlin系列之考慮使用靜態(tài)工廠方法替代構(gòu)造器(一)
翻譯系列:
- [譯]記一次Kotlin官方文檔翻譯的PR(內(nèi)聯(lián)類)
- [譯]Kotlin中內(nèi)聯(lián)類的自動(dòng)裝箱和高性能探索(二)
- [譯]Kotlin中內(nèi)聯(lián)類(inline class)完全解析(一)
- [譯]Kotlin的獨(dú)門(mén)秘籍Reified實(shí)化類型參數(shù)(上篇)
- [譯]Kotlin泛型中何時(shí)該用類型形參約束?
- [譯] 一個(gè)簡(jiǎn)單方式教你記住Kotlin的形參和實(shí)參
- [譯]Kotlin中是應(yīng)該定義函數(shù)還是定義屬性?
- [譯]如何在你的Kotlin代碼中移除所有的!!(非空斷言)
- [譯]掌握Kotlin中的標(biāo)準(zhǔn)庫(kù)函數(shù): run、with斗躏、let逝慧、also和apply
- [譯]有關(guān)Kotlin類型別名(typealias)你需要知道的一切
- [譯]Kotlin中是應(yīng)該使用序列(Sequences)還是集合(Lists)?
- [譯]Kotlin中的龜(List)兔(Sequence)賽跑
實(shí)戰(zhàn)系列: