翻譯說明:java
原標題: A few facts about Companion objects算法
原文地址: blog.kotlin-academy.com/a-few-facts…](blog.kotlin-academy.com/a-few-facts…)設計模式
原文做者: David Blanc數組
Kotlin給Java開發者帶來最大改變之一就是廢棄了static
修飾符。與Java不一樣的是在Kotlin的類中不容許你聲明靜態成員或方法。相反,你必須向類中添加Companion對象來包裝這些靜態引用: 差別看起來彷佛很小,可是它有一些明顯的不一樣。數據結構
首先,companion伴生對象是個實際對象的單例實例。你實際上能夠在你的類中聲明一個單例,而且能夠像companion伴生對象那樣去使用它。這就意味着在實際開發中,你不只僅只能使用一個靜態對象來管理你全部的靜態屬性! companion
這個關鍵字實際上只是一個快捷方式,容許你經過類名訪問該對象的內容(若是伴生對象存在一個特定的類中,而且只是用到其中的方法或屬性名稱,那麼伴生對象的類名能夠省略不寫)。就編譯而言,下面的testCompanion()
方法中的三行都是有效的語句。app
class TopLevelClass {
companion object {
fun doSomeStuff() {
...
}
}
object FakeCompanion {
fun doOtherStuff() {
...
}
}
}
fun testCompanion() {
TopLevelClass.doSomeStuff()
TopLevelClass.Companion.doSomeStuff()
TopLevelClass.FakeCompanion.doOtherStuff()
}
複製代碼
爲了兼容的公平性,companion
關鍵字還提供了更多選項,尤爲是與Java互操做性相關選項。果您嘗試在Java類中編寫相同的測試代碼,調用方式可能會略有不一樣:ide
public void testCompanion() {
TopLevelClass.Companion.doSomeStuff();
TopLevelClass.FakeCompanion.INSTANCE.doOtherStuff();
}
複製代碼
區別在於: Companion做爲Java代碼中靜態成員開放(實際上它是一個對象實例,可是因爲它的名稱是以大寫的C
開頭,因此有點存在誤導性),而FakeCompanion引用了咱們的第二個單例對象的類名。在第二個方法調用中,咱們須要使用它的INSTANCE
屬性來實際訪問Java中的實例(你能夠打開IntelliJ IDEA或AndroidStudio中的"Show Kotlin Bytecode"菜單欄,並點擊裏面"Decompile"按鈕來查看反編譯後對應的Java代碼)函數
在這兩種狀況下(不論是Kotlin仍是Java),使用伴生對象Companion
類比FakeCompanion
類那種調用語法更加簡短。此外,因爲Kotlin提供一些註解,可讓編譯器生成一些簡短的調用方式,以便於在Java代碼中依然能夠像在Kotlin中那樣簡短形式調用。post
@JvmField註解,例如告訴編譯器不要生成getter
和setter
,而是生成Java中成員。在伴生對象的做用域內使用該註解標記某個成員,它產生的反作用是標記這個成員不在伴生對象內部做用域,而是做爲一個Java最外層類的靜態成員存在。從Kotlin的角度來看,這沒有什麼太大區別,可是若是你看一下反編譯的字節代碼,你就會注意到伴生對象以及他的成員都聲明和最外層類的靜態成員處於同一級別。性能
另外一個有用的註解 @JvmStatic.這個註解容許你調用伴生對象中聲明的方法就像是調用外層的類的靜態方法同樣。可是須要注意的是:在這種狀況下,方法不會和上面的成員同樣移出伴生對象的內部做用域。由於編譯器只是向外層類中添加一個額外的靜態方法,而後在該方法內部又委託給伴生對象。
一塊兒來看一下這個簡單的Kotlin類例子:
class MyClass {
companion object {
@JvmStatic
fun aStaticFunction() {}
}
}
複製代碼
這是相應編譯後的Java簡化版代碼:
public class MyClass {
public static final MyClass.Companion Companion = new MyClass.Companion();
fun aStaticFunction() {//外層類中添加一個額外的靜態方法
Companion.aStaticFunction();//方法內部又委託給伴生對象的aStaticFunction方法
}
public static final class Companion {
public final void aStaticFunction() {}
}
}
複製代碼
這裏存在一個很是細微的差異,但在某些特殊的狀況下可能會出問題。例如,考慮一下Dagger中的module(模塊)。當定義一個Dagger模塊時,你可使用靜態方法去提高性能,可是若是你選擇這樣作,若是您的模塊包含靜態方法之外的任何內容,則編譯將失敗。因爲Kotlin在類中既包含靜態方法,也保留了靜態伴生對象,所以沒法以這種方式編寫僅僅包含靜態方法的Kotlin類。
可是不要那麼快放棄! 這並不意味着你不能這樣作,只是它須要一個稍微不一樣的處理方式:在這種特殊的狀況下,你可使用Kotlin單例(使用object對象表達式而不是class類)替換含有靜態方法的Java類並在每一個方法上使用@JvmStatic註解。以下例所示:在這種狀況下,生成的字節代碼再也不顯示任何伴生對象,靜態方法會附加到類中。
@Module
object MyModule {
@Provides
@Singleton
@JvmStatic
fun provideSomething(anObject: MyObject): MyInterface {
return myObject
}
}
複製代碼
這又讓你再一次明白了伴生對象僅僅是單例對象的一個特例。但它至少代表與許多人的認知是相反的,你不必定須要一個伴生對象來維護靜態方法或靜態變量。你甚至根本不須要一個對象來維護,只要考慮頂層函數或常量:它們將做爲靜態成員被包含在一個自動生成的類中(默認狀況下,例如MyFileKt會做爲MyFile.kt文件生成的類名,通常生成類名以Kt爲後綴結尾)
咱們有點偏離這篇文章的主題了,因此讓咱們繼續回到伴生對象上來。如今你已經瞭解了伴生對象實質就是對象,也應該意識到它開放了更多的可能性,例如繼承和多態。
這意味着你的伴生對象並非沒有類型或父類的匿名對象。它不只能夠擁有父類,並且它甚至能夠實現接口以及含有對象名。它不須要被稱爲companion。這就是爲何你能夠這樣寫一個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)
}
}
複製代碼
這裏, 伴生對象名爲CREATOR,它實現了Android中的Parcelable.Creator接口,容許遵照Parcelable約定,同時保持比使用@JvmField註釋在伴隨對象內添加Creator對象更直觀。Kotlin中引入了@Parcelize註解,以便於能夠得到全部樣板代碼,可是在這不是重點...
爲了使它變得更簡潔,若是你的伴生對象能夠實現接口,它甚至可使用Kotlin中的代理來執行此操做:
class MyObject {
companion object : Runnable by MyRunnable()
}
複製代碼
這將容許您同時向多個對象中添加靜態方法!請注意,伴生對象在這種狀況下甚至不須要做用域體,由於它是由代理提供的。
最後但一樣重要的是,你能夠爲伴生對象定義擴展函數! 這就意味着你能夠在現有的類中添加靜態方法或靜態屬性,以下例所示:
class MyObject {
companion object
fun useCompanionExtension() {
someExtension()
}
}
fun MyObject.Companion.someExtension() {}//定義擴展函數
複製代碼
這樣作有什麼意義?我真的不知道。雖然Marcin Moskala建議使用此操做將靜態工廠方法以Companion的擴展函數的形式添加到類中。
總而言之,伴生對象不只僅是爲了給缺乏static
修飾符的使用場景提供解決方案:
與大多數場景同樣,Kotlin意味着在你設計過程須要有一點點轉變,但與Java相比,它並無真正限制你的選擇。若是有的話,也會經過提供一些新的、更簡潔的方式讓你去使用它。
歡迎關注Kotlin開發者聯盟,這裏有最新Kotlin技術文章,每週會不按期翻譯一篇Kotlin國外技術文章。若是你也喜歡Kotlin,歡迎加入咱們~~~
Kotlin邂逅設計模式系列:
數據結構與算法系列:
Kotlin 原創系列:
Effective Kotlin翻譯系列
翻譯系列:
實戰系列: