Kotlin教學 | 從零開始學Kotlin | Kotlin入門 | CH13: 物件表達式和物件宣告

物件表達式和物件宣告

在 Kotlin 中,物件表達式和物件宣告提供靈活的方式創建和使用物件以滿足不同的使用場景。

  • 物件表達式:用於創建匿名物件,適合於一次性使用的場景。
  • 物件宣告:用於創建一個單例物件,即一個類別只存在唯一一個物件實體。
  • 伴生物件:用於在類別內部創建一個靜態成員的容器,這些成員可以透過類別名稱直接訪問。

物件表達式

物件表達式用於創建匿名類別的物件,即那些沒有透過類別宣告產生的物件,又稱為匿名物件。
匿名物件常用於使用只有一次性類別的場合。
匿名物件可以繼承一個或多個父類別(或介面)。

val anonymousObj = object: ParentA(...), ParentB(...) {
    // 成員變量(屬性)
    var mutableProperty: Type1 = initialValue
    val immutableProperty: Type2 = initialValue

    // 成員函數
    fun functionName() {
        // 函數體
    }

    // 初始化塊
    init {
        // 初始化邏輯
    }
}
  • object:關鍵字用於宣告匿名物件。
  • ParentA(...), ParentB(...)ParentAParentB 分別表示匿名物件所繼承的父類別或實現的介面。可以提供適當的構造函數參數(如果有的話)。
  • 匿名物件主體 {}:在大括號內部,你可以定義物件的成員變量(屬性)、成員函數,以及一個初始化塊。
  • 成員變量(屬性):宣告物件的變量。
  • 成員函數:宣告物件的函數。
  • init { ... }:初始化塊,在匿名物件被實例化時執行初始化邏輯。

Example

創建匿名物件

val helloWorld = object {
    val hello = "Hello"
    val world = "World"
    override fun toString() = "$hello $world"
}

print(helloWorld)

匿名物件繼承父類別和覆蓋函數

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { /*...*/ }

    override fun mouseEntered(e: MouseEvent) { /*...*/ }
})

匿名物件繼承父類別和介面

open class A(x: Int) {
    public open val y: Int = x
}

interface B { /*...*/ }

val ab: A = object : A(1), B {
    override val y = 15
}

匿名物件返回

被匿名物件返回的函數或變量,必須是區域(local)或私有的(private)才可以存取匿名物件的成員。

如下例子,getObject().xgetObject()是私有的所以可以存取匿名物件的屬性 x

class C {
    private fun getObject() = object {
        val x: String = "x"
    }

    fun printX() {
        println(getObject().x) // 可以存取 x
    }
}

返回的型別

如果被匿名物件返回的函數或變量是公共的(public),其型別將是:

  • 如果匿名物件沒有宣告父類別,則為 Any
  • 如果有一個父類別,則為該父類別。
  • 如果有多個宣告的父類別,則為返回指定的父類別。
  • 在所有情況下,匿名物件中新增的成員都不可存取。
interface A {
    fun funFromA() {}
}
interface B

class C {
    // 返回型別為 Any; x 不可存取
    fun getObject() = object {
        val x: String = "x"
    }

    // 返回型別為 A; funFromA() 可以存取, x 不可存取
    fun getObjectA() = object: A {
        override fun funFromA() {}
        val x: String = "x"
    }

    // 返回型別為 B; funFromA() 和 x 都不可存取
    fun getObjectB(): B = object: A, B {
        override fun funFromA() {}
        val x: String = "x"
    }
}

從匿名物件存取外圍作用域變量

物件表達式中的程式碼可以存取封閉作用域中的變量:

下面例子中,匿名物件可以存取並修改 clickCountenterCount 變量,這兩個變量定義在函數 countClicks 的作用域內。

fun countClicks(window: JComponent) {
    var clickCount = 0
    var enterCount = 0

    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++
        }

        override fun mouseEntered(e: MouseEvent) {
            enterCount++
        }
    })
    // ...
}

物件宣告

單例模式

Kotlin的物件宣告用於提供一個簡單的方式來實現單例模式,省去編寫額外的程式碼。
單例模式(Singleton Pattern)是一種設計模式,表示一個類別只會存在唯一一個物件。
如果使用一般的class語法來設計,會需要額外的程式碼編寫和維護,例如: 需要私有化的建構子,提供統一個getInstance函數等等。

object ObjectName {
    // 成員變量(屬性)
    var mutableProperty: Type1 = initialValue
    val immutableProperty: Type2 = initialValue

    // 成員函數
    fun functionName() {
        // 函數體
    }

    // 初始化塊
    init {
        // 初始化邏輯
    }
}
  • object:關鍵字用於宣告物件。
  • ObjectName:物件的名稱。
  • 物件主體 {}:在大括號內部,你可以定義物件的成員變量(屬性)、成員函數,以及一個初始化塊。

特性

  • 第一次存取該物件時,會完成多線程安全(thread-safe)的初始化,之後都會使用第一個(同一個)物件。
  • 直接使用物件名稱存取成員,無需實體化。
  • 物件宣告不能在函數內部宣告,但可以巢狀在其他物件宣告或非內部(inner)類別中。

Example

假設我們需要一個全局存取的配置管理器:

object ConfigManager {
    var url: String = "http://example.com"
    var timeout: Int = 1000
    
    fun printConfig() {
        println("URL: $url, Timeout: $timeout")
    }
}

更新 ConfigManagerurl 屬性並打印出新的配置。

ConfigManager.url = "http://newexample.com"
ConfigManager.printConfig()

伴生物件

伴生物件用來提供與類別的物件不相關,但是與類別本身相關的函數或屬性。
通過在類別中宣告伴生物件來實現與類別相關的函數或屬性。
允許你在不需要創造類別物件的情況下存取這些類別相關的功能。

class ClassName {
    companion object CompanionName {
        // 成員變量(屬性)
        var mutableProperty: Type1 = initialValue
        val immutableProperty: Type2 = initialValue

        // 成員函數
        fun functionName() {
            // 函數體
        }

        // 初始化塊
        init {
            // 初始化邏輯
        }
    }
}
  • class:關鍵字用於宣告類別。
  • ClassName:類別名稱。
  • companion object:關鍵字組合,表示這是一個伴生物件的宣告。
  • CompanionName:伴生物件的名稱。可以省略,如果省略,則默認名稱為 Companion
  • 成員變量(屬性)和成員函數:伴生物件內部可以宣告變量和函數。
    類別成員可以存取對應伴生物件的私有成員。
  • init { ... }:初始化塊,在匿名物件被實例化時執行初始化邏輯。

使用伴生物件

使用類別名稱等同使用伴生物件。
因此,可以直接使用類別名稱存取伴生物件的成員。

class MyClass1 {
    companion object Named { 
        init{ println("MyClass1")}
    }
}

class MyClass2 {
    companion object { 
        init{ println("MyClass2")}

        val name: String = "Ronald"
    }
}

fun main() {
    val x = MyClass1 // 使用 MyClass1 的伴生物件
    var y = MyClass2 // 使用 MyClass2 的伴生物件
    println(y.name)

    y = MyClass2.Companion // 使用 MyClass2 的伴生物件   
    println(y.name)
}

伴生物件的常見應用

伴生物件常用於實現類似於其他語言中的靜態方法和靜態屬性。
例如實現工廠方法的設計模式等,你可能希望有一種方法來創建類別的實例,而不需要直接使用構造函數。

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

val instance = MyClass.create() // 省略Factory,等同 MyClass.Factory.create()

伴生物件的成員看起來類似於其他語言中的靜態成員,但實際上它們是真實物件,這表示它們可以實現介面。

interface Factory<T> {
    fun create(): T
}

class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass = MyClass()
    }
}

val f: Factory<MyClass> = MyClass

Reference

https://kotlinlang.org/docs/object-declarations.html

發佈留言