top
Loading...
Kotlin 對象表達式和對象聲明

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.sharebody.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.sharebody.com"
    println(s1.url)
    println(s2.url)
}

輸出結果為:

www.sharebody.com
www.sharebody.com

對象可以有超類型:

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

與對象表達式不同,當對象聲明在另一個類的內部時,這個對象併不能通過外部類的實例訪問到該對象,而只能通過類名來訪問,同樣該對象也不能直接訪問到外部類的方法和變量。

class Site {
    var name = "教程"
    object DeskTop{
        var url = "www.sharebody.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 靜態初始化器的語義相匹配

北斗有巢氏 有巢氏北斗