Kotlin 物件表示式和物件宣告

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 main(args: Array<String>) {
    val site = object {
        var name: String = "入門教學"
        var url: String = "www.itread01.com"
    }
    println(site.name)
    println(site.url)
}

請注意,匿名物件可以用作只在本地和私有作用域中宣告的型別。如果你使用匿名物件作為公有函式的 返回型別或者用作公有屬性的型別,那麼該函式或屬性的實際型別 會是匿名物件宣告的超型別,如果你沒有宣告任何超型別,就會是 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”
    }
}

在物件表達中可以方便的訪問到作用域中的其他變數:

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 使用 object 關鍵字來宣告一個物件。

Kotlin 中我們可以方便的通過物件宣告來獲得一個單例。

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

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

引用該物件,我們直接使用其名稱即可:

DataProviderManager.registerDataProvider(……)

當然你也可以定義一個變數來獲取獲取這個物件,當時當你定義兩個不同的變數來獲取這個物件時,你會發現你並不能得到兩個不同的變數。也就是說通過這種方式,我們獲得一個單例。

var data1 = DataProviderManager
var data2 = DataProviderManager
data1.name = "test"
print("data1 name = ${data2.name}")  

例項

以下例項中,兩個物件都輸出了同一個 url 地址:

object Site {
    var url:String = ""
    val name: String = "入門教學"
}
fun main(args: Array<String>) {
    var s1 =  Site
    var s2 = Site
    s1.url = "www.itread01.com"
    println(s1.url)
    println(s2.url)
}

輸出結果為:

www.itread01.com
www.itread01.com

物件可以有超型別:

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

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

與物件表示式不同,當物件宣告在另一個類的內部時,這個物件並不能通過外部類的例項訪問到該物件,而只能通過類名來訪問,同樣該物件也不能直接訪問到外部類的方法和變數。

class Site {
    var name = "入門教學"
    object DeskTop{
        var url = "www.itread01.com"
        fun showName(){
            print{"desk legs $name"} // 錯誤,不能訪問到外部類的方法和變數
        }
    }
}
fun main(args: Array<String>) {
    var site = Site()
    site.DeskTop.url // 錯誤,不能通過外部類的例項訪問到該物件
    Site.DeskTop.url // 正確
}

伴生物件

類內部的物件宣告可以用 companion 關鍵字標記,這樣它就與外部類關聯在一起,我們就可以直接通過外部類訪問到物件的內部元素。

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

val instance = MyClass.create()   // 訪問到物件的內部元素

我們可以省略掉該物件的物件名,然後使用 Companion 替代需要宣告的物件名:

class MyClass {
    companion object {
    }
}

val x = MyClass.Companion

注意:一個類裡面只能宣告一個內部關聯物件,即關鍵字 companion 只能使用一次。

請伴生物件的成員看起來像其他語言的靜態成員,但在執行時他們仍然是真實物件的例項成員。例如還可以實現介面:

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


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

物件表示式和物件宣告之間的語義差異

物件表示式和物件宣告之間有一個重要的語義差別:

  • 物件表示式是在使用他們的地方立即執行的

  • 物件宣告是在第一次被訪問到時延遲初始化的

  • 伴生物件的初始化是在相應的類被載入(解析)時,與 Java 靜態初始化器的語義相匹配