Kotlin對象表達式和對象聲明

對象表達式和對象聲明

有時候,我們需要創建一個對某個類做了輕微改動的類的對象,而不用爲之顯式聲明新的子類。
Java 用匿名內部類 處理這種情況。
Kotlin 用對象表達式對象聲明對這個概念稍微概括了下。

對象表達式

要創建一個繼承自某個(或某些)類型的匿名類的對象,我們會這麼寫:

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
}

任何時候,如果我們只需要「一個對象而已」,並不需要特殊超類型,那麼我們可以簡單地寫:

fun foo() {
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
}

請注意,匿名對象可以用作只在本地和私有作用域中聲明的類型。如果你使用匿名對象作爲公有函數的
返回類型或者用作公有屬性的類型,那麼該函數或屬性的實際類型
會是匿名對象聲明的超類型,如果你沒有聲明任何超類型,就會是 Any。在匿名對象
中添加的成員將無法訪問。

class C {
    // 私有函數,所以其返回類型是匿名對象類型
    private fun foo() = object {
        val x: String = "x"
    }

    // 公有函數,所以其返回類型是 Any
    fun publicFoo() = object {
        val x: String = "x"
    }

    fun bar() {
        val x1 = foo().x        // 沒問題
        val x2 = publicFoo().x  // 錯誤:未能解析的引用「x」
    }
}

就像 Java 匿名內部類一樣,對象表達式中的代碼可以訪問來自包含它的作用域的變量。
(與 Java 不同的是,這不僅限於 final 變量。)

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(繼 Scala 之後)使單例聲明變得很容易:

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ……
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ……
}

這稱爲對象聲明。並且它總是在 object{: .keyword } 關鍵字後跟一個名稱。
就像變量聲明一樣,對象聲明不是一個表達式,不能用在賦值語句的右邊。

要引用該對象,我們直接使用其名稱即可:

DataProviderManager.registerDataProvider(……)

這些對象可以有超類型:

object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ……
    }

    override fun mouseEntered(e: MouseEvent) {
        // ……
    }
}

注意:對象聲明不能在局部作用域(即直接嵌套在函數內部),但是它們可以嵌套到其他對象聲明或非內部類中。

伴生對象

類內部的對象聲明可以用 companion{: .keyword } 關鍵字標記:

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

該伴生對象的成員可通過只使用類名作爲限定符來調用:

val instance = MyClass.create()

可以省略伴生對象的名稱,在這種情況下將使用名稱 Companion

class MyClass {
    companion object {
    }
}

val x = MyClass.Companion

請注意,即使伴生對象的成員看起來像其他語言的靜態成員,在運行時他們
仍然是真實對象的實例成員,而且,例如還可以實現接口:

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


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

當然,在 JVM 平臺,如果使用 [@JvmStatic](https://github.com/JvmStatic "@JvmStatic") 註解,你可以將伴生對象的成員生成爲真正的
靜態方法和字段。更詳細信息請參見Java 互操作性一節

對象表達式和對象聲明之間的語義差異

對象表達式和對象聲明之間有一個重要的語義差別:

  • 對象表達式是在使用他們的地方立即執行(及初始化)的
  • 對象聲明是在第一次被訪問到時延遲初始化的
  • 伴生對象的初始化是在相應的類被加載(解析)時,與 Java 靜態初始化器的語義相匹配