你已經知道如何為類創建單例對象(singleton)。不過,在很多情況下,你只需要為某個類維護一個單例,這時候使用類的完整名字會顯得冗長。比如,你可能只需要存儲一個公共的屬性。這種情況下,可以用 Kotlin 的另一個特性 —— companion object(伴生對象)。
伴生對象(Companion object)
在一個類內部,可以聲明一個用 companion
關鍵字標記的對象:
class Player(val id: Int) {companion object Properties {/* 默認玩家速度 - 每回合移動 7 格 */val defaultSpeed = 7fun calcMovePenalty(cell: Int): Int {/* 計算移動速度的懲罰 */}}
}/* 輸出 7 */
println(Player.Properties.defaultSpeed)
解釋:
伴生對象是綁定在外部類上的單例,必須通過外部類訪問它。它表明該對象與外部類有緊密聯系。比如,可以把所有玩家的默認速度存在 Player
類的伴生對象里。每個 Player
實例都會持有伴生對象的引用,訪問時都會得到這個唯一實例。
省略伴生對象名字
我們也可以不給伴生對象命名,這樣訪問時更加簡潔:
class Player(val id: Int) {companion object {val defaultSpeed = 7fun calcMovePenalty(cell: Int): Int {/* 計算移動懲罰 */}}
}/* 輸出 7 */
println(Player.defaultSpeed)
解釋:
省略名字后,仍然可以通過外部類直接訪問伴生對象的成員。如果需要,也可以用默認名字 Companion
訪問:
/* 依然輸出 7 */
println(Player.Companion.defaultSpeed)
伴生對象與外部類
伴生對象與外部類聯系非常緊密。在外部類中,可以直接使用伴生對象的屬性和方法:
class Deck {companion object {val size = 10val height = 2fun volume(bottom: Int, height: Int) = bottom * height}val square = size * size // 100val volume = volume(square, height) // 200
}
同名屬性的遮蔽(Shadowing)
如果外部類中有與伴生對象同名的屬性,會“遮蔽”伴生對象的同名屬性:
class Deck {companion object {val size = 10}val size = 2val square = size * size // 4,使用的是外部類的 size
}
如果想訪問伴生對象的 size
,需要明確使用伴生對象的名字:
class Deck {companion object {val size = 10}val size = 2val square = Companion.size * Companion.size // 100
}
伴生對象不能訪問外部類實例成員
和嵌套對象類似,伴生對象不能訪問外部類的實例屬性和方法:
class Deck() {val size = 2object Properties {val defaultSize = size // 錯誤,無法訪問外部類的實例變量}
}
伴生對象的限制
- 每個類最多只能有一個伴生對象,即使起不同名字也不行:
class BadClass {companion object Properties {}companion object Factory {}
}
// 編譯錯誤:每個類只能有一個伴生對象
- 可以有一個伴生對象,同時擁有多個嵌套對象:
class Player(val id: Int) {companion object Properties {val defaultSpeed = 7fun calcMovePenalty(cell: Int): Int {// ...}}object Factory {fun create(playerId: Int): Player {return Player(playerId)}}
}println(Player.Properties.defaultSpeed) // 7
println(Player.defaultSpeed) // 7
println(Player.Factory.create(13).id) // 13
- 伴生對象不能定義在另一個單例對象或伴生對象內部,因為這會違反全局訪問的原則:
object OuterSingleton {companion object InnerSingleton { // 編譯錯誤,伴生對象不能嵌套在對象中}
}
與其他語言的對比
如果你來自其他語言,可能會覺得伴生對象有點陌生。它類似于 Java 或 C++ 中的 static
成員,static
表示成員屬于類本身,而不是實例。比如,Java 中:
class Dog {public static int numOfPaws = 4;public static String createSound() {return "WUF-WUF";}
}/* 輸出 WUF-WUF */
System.out.println(Dog.createSound());
Kotlin 沒有 static
關鍵字,推薦用伴生對象來實現類似功能:
class Dog {companion object {val numOfPaws: Int = 4fun createSound(): String = "WUF-WUF"}
}/* 輸出 WUF-WUF */
println(Dog.createSound())
總結
-
伴生對象是和類緊密關聯的單例對象。
-
它是組織類級別數據和方法的好方式。
-
在外部類中可以直接訪問伴生對象的成員,反之則不行。
-
每個類只能有一個伴生對象。
-
它是 Kotlin 中實現類靜態成員的推薦做法。