- Published on
Kotlin object 關鍵字:一個關鍵字,三種超能力

在 Kotlin 裡,object 這個關鍵字值得停下來想一想。它不只是個保留字或語法糖,而是直接把 Singleton、static 替代方案、匿名物件這三種東西融進語言層級,一個關鍵字搞定
Object Declaration — 語言層級的 Singleton
在 Java 裡寫一個 thread-safe 的 Singleton,得處理 private constructor、static instance、synchronized 或 double-checked locking。Kotlin 用一行 object 宣告就解決了
object DatabaseConfig {
val url = "jdbc:mysql://localhost:3306/mydb"
val maxConnections = 10
fun connect() {
println("Connecting to $url")
}
}
使用時直接透過名稱存取,不需要 new,也不需要呼叫 getInstance()
DatabaseConfig.connect()
println(DatabaseConfig.maxConnections)
底層實作 (Kotlin/JVM)
在 Kotlin/JVM 上,Kotlin 編譯器把 object declaration 編譯成帶有 static INSTANCE 欄位的 Java class,透過 static initializer 保證初始化的 thread safety。反編譯後長這樣
public final class DatabaseConfig {
public static final DatabaseConfig INSTANCE;
static {
INSTANCE = new DatabaseConfig();
}
private DatabaseConfig() {}
// ... 其他方法
}
在 JVM 上,它是 lazy 的 (首次存取時才觸發 class loading),也是 thread-safe 的 (由 JVM 的 class loading 機制保證)
Object Declaration 也能繼承和實作介面
Object declaration 是完整的物件,可以繼承 class 和實作介面
interface Logger {
fun log(message: String)
}
object ConsoleLogger : Logger {
override fun log(message: String) {
println("[LOG] $message")
}
}
這樣 Singleton 就可以被依賴注入、被抽換、被測試,不再像傳統 Singleton 那樣難搞
Companion Object — 比 static 更強大的設計
Kotlin 刻意拿掉了 static 關鍵字,改用 companion object — 宣告在 class 內部的特殊 object
class User private constructor(val name: String, val email: String) {
companion object {
fun fromEmail(email: String): User {
val name = email.substringBefore("@")
return User(name, email)
}
fun fromMap(data: Map<String, String>): User {
return User(data["name"]!!, data["email"]!!)
}
}
}
呼叫方式跟 Java 的 static method 一樣
val user = User.fromEmail("[email protected]")
為什麼不直接用 static?
因為 companion object 是真正的物件實例,所以能做到 static 做不到的事
可以有名稱
class User(val name: String) {
companion object Factory {
fun create(name: String) = User(name)
}
}
// 兩種呼叫方式都可以
User.create("Cash")
User.Factory.create("Cash")
名稱可以省略,省略時預設名稱為 Companion
可以實作介面
interface JsonDeserializer<T> {
fun fromJson(json: String): T
}
class User(val name: String) {
companion object : JsonDeserializer<User> {
override fun fromJson(json: String): User {
return User(json) // 簡化的解析邏輯
}
}
}
因為 companion object 本身是物件,可以直接當成參數傳遞
fun <T> parse(json: String, deserializer: JsonDeserializer<T>): T {
return deserializer.fromJson(json)
}
// 直接傳 User,而不是 User.Companion 或 User::class
val user = parse("""{"name":"Cash"}""", User)
寫 User 看起來像是在傳 class 本身,實際上傳的是它背後的 companion object。只要其他 class 的 companion object 也實作同一個介面,就能用同一個 parse() 處理不同型別
class Product(val id: String) {
companion object : JsonDeserializer<Product> {
override fun fromJson(json: String): Product {
return Product(json)
}
}
}
// User 和 Product 都能傳進同一個 parse()
val user = parse("""{"name":"Cash"}""", User)
val product = parse("""{"id":"A001"}""", Product)
「直接傳 class 名稱」這種寫法在其他語言幾乎看不到,這正是 companion object 實作介面帶來的好處
可以被擴充
class User(val name: String) {
companion object
}
// 在別的檔案裡擴充
fun User.Companion.fromCsv(csv: String): User {
val name = csv.split(",")[0]
return User(name)
}
// 使用
val user = User.fromCsv("Cash,[email protected]")
不用動原始 class,就能替它的「static API」加新功能
Object Expression — 更靈活的匿名物件
Java 的 anonymous inner class 有兩個限制:只能繼承一個 class 或實作一個介面,而且不能修改被捕獲的區域變數。Kotlin 的 object expression 兩個都解了
基本用法
interface ClickListener {
fun onClick(source: String)
}
val listener = object : ClickListener {
override fun onClick(source: String) {
println("Clicked: $source")
}
}
同時實作多個介面
val hybrid : Runnable = object : Runnable, Comparable<String> {
override fun run() {
println("Running")
}
override fun compareTo(other: String): Int = 0
}
不繼承任何東西的匿名物件
只是想打包幾個值,不想為此宣告一個 class:
fun getResult(): Any {
val result = object {
val status = "success"
val code = 200
}
return result
}
要注意,這種匿名物件的型別只在 local 和 private 的場景下才能被正確識別。透過 public function 回傳時,型別會退化成 Any,屬性就存取不到了
可以修改外層變數
Java 的匿名內部類別只能存取 effectively final 的區域變數,Kotlin 沒有這個限制
var clickCount = 0
val listener = object : ClickListener {
override fun onClick(source: String) {
clickCount++ // 直接修改外層變數
println("Clicked $clickCount times")
}
}
在 Kotlin/JVM 上,底層是把變數包裝在一個 Ref 物件裡,所以能突破 Java 的 effectively final 限制
三種用法的比較
| 特性 | Object Declaration | Companion Object | Object Expression |
|---|---|---|---|
| 主要用途 | Singleton | 類似 static 的替代方案 | 匿名物件 |
| 生命週期 | 全域唯一,首次存取時初始化 | 隨外層類別首次使用時初始化 | 隨所在的作用域 |
| 可以有名稱 | 必須有 | 可選 | 無 |
| 可以實作介面 | 可以 | 可以 | 可以 |
| 可以繼承 class | 可以 | 可以 | 可以 |
| Thread-safe 初始化 (JVM) | 可以 | 可以 | 不適用 |
| 可以被擴充 (extension) | 可以 | 可以 | 無法 |
| 可以存取外層 class 成員 | 不適用 | 可以 | 可以 |
總結
Kotlin 的 object 把三種在其他語言裡要靠 pattern 或 workaround 才能搞定的東西,直接提升到語言層級。Object Declaration 讓 Singleton 不再需要樣板程式碼。Companion Object 用真正的物件取代了 static,順便帶來多型、介面實作和擴充函式。Object Expression 解除了 Java 匿名內部類別的兩個限制
一個關鍵字,三種用法,設計上沒有浪費
圖片來源:AI 產生