iT邦幫忙

0

來看Kotlin官方文件,什麼是 Object expressions and declarations

  • 分享至 

  • xImage
  •  

Object expressions and declarations

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

這邊會介紹到什麼是

Object expressions
Object declarations

還有什麼是

anonymous Object expressions

以及他的衍生,也是大家常用的類似static class宣告方式

Companion Object
anonymous Companion Object


今天在Twitter(x)上看到有一個的發文,他說他在一場會議看到了一個寫法

Creating anonymous objects from scratch

val helloWorld = object {
    val hello = "Hello"
    val world = "World"
    // object expressions extend Any, so `override` is required on `toString()`
    override fun toString() = "$hello $world"
}
println(helloWorld.hello)

就可以得到

Hello

會很訝異的是,匿名類別我們知道,但沒想到除了宣告匿名類別外,居然可以額外添加成員功能?

其實後來仔細想一想,本來就可以,你一定也使用過匿名類別的產生,差別在於這邊會多上一個:去實做這個OnViewClickListener,但是確不知道其實也可以用來產生一個沒實作其他類別的物件寫法,可以看下面宣告方式,你一定很熟悉

view.setOnClickListener(object: OnViewClickListener() { 
 ...
})

然而你會發現,以前也不知道為什麼,就是固定寫object :這樣方式,來匿名實做介面使用,但其實這是有使用原理的

先看看官方對於Object express的描述

Object expressions create objects of anonymous classes, that is, classes that aren't explicitly declared with the class declaration. Such classes are useful for one-time use. You can define them from scratch, inherit from existing classes, or implement interfaces. Instances of anonymous classes are also called anonymous objects because they are defined by an expression, not a name.

當中提到,類別不是明確的宣告於某一個類別中,這樣的類別在一次性的時候是有用的. 你可以定義他在來繼承存在的類別或者介面實作。匿名類別的實例被稱為匿名物件,因為他們被定義成一種表示,而非一個名稱

有這樣定義就更明確的,可以往下看

Inheriting anonymous objects from supertypes

其實就是這個章節提到,也稱為Inheriting anonymous objects from supertypes ,官方的範例是這樣

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 { // <-----同時匿名實作或繼承A與B
    override val y = 15
}

也就是Android開發者常見,平常都在使用的匿名實做OnViewClickListener的寫法,因此如果今天我們不進行介面實作,他就會變成anonymous Object expressions,也就是剛剛最上面的寫法

val helloWorld = object {
    val hello = "Hello"
    val world = "World"
    // object expressions extend Any, so `override` is required on `toString()`
    override fun toString() = "$hello $world"
}
println(helloWorld.hello)

仔細比對一下,是不是很熟悉,也不會覺得這樣寫法很特別了,

但你可能會問

Q.如果沒有實作介面或者繼承,會有什麼原因會需要使用這樣的寫法?

個人也是沒用過的(畢竟我也才剛了解),但根據官方文件,如果你是one-time use,或者你只是要在某一個function scope中定義一個有屬性有方法的匿名類別,就很適合這樣使用,我想到的就是你在一個function中剛好要處理一個複雜的case,也懶得去獨立建立一個data class然後還要想一個命名給他的時候,就非常適合用匿名物件(anonymous object),譬如類似這樣,但這可能不是一個很好的範例,等我想到再補充,總之你可以在一個function中去再次整理你的程式碼,聚合,當然用透過inner function也是可以

fun test(val outterName: String,val outterAge: Int? = null) {
    // 在一個function裏面,有多個欄位想要放在一起
    // 但又不想額外宣告一個private fun來增加本身類別的function 暴露
    val result = object {
        val name = outterName
        val age = outterAge ?: "Uknow"
        
        fun getDisplay() = "$name -($age)"
    }
    
    // 最終直接這樣使用
    textView.text = result.getDisplay()
}

Accessing variables from anonymous objects

另外是如果在同一個scope中,匿名物件是可以直接去存取同一個scope的變數

fun countClicks(window: JComponent) {
    var clickCount = 0 // <-----------在fun scope中的變數
    var enterCount = 0 

    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++  // <------------在匿名物件中可以直接讀取或者修改是沒問題的
        }

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

如同上面,他可以直接存取上面的clickCount的變數


Object declarations 物件宣告

這寫法應該是大家都用過的,也就是java的static class要轉成kotlin的時候都會透過object declarations來宣告使用,並且具有Singleton的特性,與java static class又不同,他是可以被當成參數傳遞的

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

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

上面的方法就稱為object declaration,中文應該就可以稱為物件宣告吧? 這樣的方式就不是匿名物件,當然他也可以用來實作或繼承其他類別

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

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

所以其實我覺得官方文件應該是要先介紹這個,然後可以再提到他如果省略前方的object name,就可以變成匿名物件,應該會比較好理解

Data objects 資料物件

另外他也可以像是data class一樣變成data objects

data object MyDataObject {
    val x: Int = 3
}

fun main() {
    println(MyDataObject) // MyDataObject
    println(MyDataObject.x) // 3
}

但差別在於,因為物件實例沒辦法有建構子,所以也不會有所謂的建構子參數,比較適合用來宣告成一個具有不可變的常數物件的感覺


Companion objects

這個應該也是很常見的一個語法,也就是在class中另外宣告一個Companion objects,可以匿名,也可以進行命名,而中文好像還沒有官方的正式命名,可以稱為伴生、協同、共生於class上的物件名稱,也就是他寄身於於本的class name上,譬如下方式MyClass,他就會存在於MyClass中,所以這就看自己怎麼翻譯理解囉

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

透過這樣產生實例

val instance = MyClass.create()

也可以透過Companion取出Companion objects

class MyClass {
    companion object { }
}

val x = MyClass.Companion

接著你也可以不命名,變成匿名Companion objects

class MyClass1 {
    companion object Named { }
}

val x = MyClass1

class MyClass2 {
    companion object { }
}

val y = MyClass2

另外比較少見的,Companion objects也可以實作另一個介面在

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

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

val f: Factory<MyClass> = MyClass

總整理

以上就是關於官方文件談到的Object expressions and declarations的章節內容,有很多在書本上沒有提到的部分,或者大部分都沒看過的額外一些用法,在來整理一下

  1. 所以回過頭來先知道 Object 使用方式

https://ithelp.ithome.com.tw/upload/images/20231005/20125654CfTW3Lsnhi.png

  1. 不宣告名稱他就變成一個匿名物件

https://ithelp.ithome.com.tw/upload/images/20231005/20125654huMrvUERyI.png

  1. 將匿名物件直接產生來使用
val manager = object {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }

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

使用

manager.registerDataProvider(..) // 直接呼叫使用

或是

  1. 實做另一個介面,有物件名稱

https://ithelp.ithome.com.tw/upload/images/20231005/20125654RnRDo2PCRk.png

  1. 實做另一個介面,改成匿名物件

https://ithelp.ithome.com.tw/upload/images/20231005/201256542MIPHGlySw.png

  1. 簡化一下,直接放進method中就變成常見的匿名實作介面的寫法
view.setMouseAdapter(object: MouseAdapter {
    override fun mouseClicked(e: MouseEvent) { /*...*/ }

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

    fun registerDataProvider(provider: DataProvider) {
        // ...
    }

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

這樣應該就會更清楚了,可以直接弄懂Object declared後了解什麼是Object expression,我個人也蠻喜歡到官方網站去挖寶的,下次有看到其他覺得特別或者少見的語法,在來分享,如果有發現內容有任何錯誤,也歡迎留言給予建議。


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言