Kotlin 對象表達式和對象聲明

有時候,我們需要對某個類進行輕微的改動(比如重寫或實現某個方法等),而又不用再顯示聲明新的子類,這時候,我們是怎么處理的呢?

Java 中提供了匿名內部類來應對這種情況

Kotlin 中則采用對象表達式對象聲明來解決.

對象表達式

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

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
    // ……
    } o
    verride fun mouseEntered(e: MouseEvent) {
    // ……
    }
})

即:

object:TypeClass(){
    ....
}

對象可以繼承于某個基類,或者實現其他接口,如果父類有一個構造函數,則必須傳遞適當的構造函數參數給它。多個超類型和接口可以用逗號分隔:

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.runoob.com"
    }
    println(site.name)
    println(site.url)
}

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

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

Note :匿名對象可以用作只在本地和私有作用域中聲明的類型。如果你使用匿名對象作為公有函數的 返回類型或者用作公有屬性的類型,那么該函數或屬性的實際類型 會是匿名對象聲明的超類型,如果你沒有聲明任何超類型,就會是 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 一樣,Kotlin 在對象表達中也可以方便的訪問到作用域中的其他變量,唯一的區別是Java需要對局部變量進行 final 修飾,Kotlin則不必:

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 來聲明一個對象,下面我們來創建一個單例:

package com.talent.kotlin.example

object SingleTonExample {
    
}

你沒看錯,聲明一個單例就是這么簡單.那么在字節碼中它被編繹成什么樣了呢?用jd-gui查看如下:

public final class SingleTonExample
{
  public static final SingleTonExample INSTANCE;

  //類的加載最后一步是初始化,即對類的靜態變量和靜態代碼塊執行初始化工作, 這里的靜態代碼塊獲取一個Singleton()對象, 并賦值給INSTANCE靜態變量
  static
  {
    new SingleTonExample();
  }
  private SingleTonExample()
  {
    INSTANCE = (SingleTonExample)this;
  }
}

看吧!就是java中的寫法。

現在我們添加一個方法,再來訪問:

package com.talent.kotlin.example

object SingleTonExample {

    fun handleMessage(msg:String){
        print("receive message:$msg")
    }
}

調用:

fun doMain(){
    SingleTonExample.handleMessage("msg")
}

當然你也可以能過定義一個變量來調用它,像下面這樣:

fun doMain(){
    var single  = SingleTonExample
    single.handleMessage("msg")
}

那么還有沒有其它的單例方式呢?當然是有的:

  • 通過伴生對象(下面會講伴生對象這個概念)
package com.talent.kotlin.example

class SingleTonExample private constructor(){

    companion object {
        val instance = SingleTonExample()
    }

    fun handleMessage(msg:String){
        print("receive message:$msg")
    }
    //調用
    fun call(){
        SingleTonExample.instance.handleMessage("msg")
    }
}
  • 懶漢式的單例實現方法(有沒有想起Java的餓漢式,懶漢式?)
package com.talent.kotlin.example

class SingleTonExample private constructor(){

    private object Holder{
        val single = SingleTonExample()
    }

    companion object {
       val instance :SingleTonExample by lazy { Holder.single }
    }

    fun handleMessage(msg:String){
        print("receive message:$msg")
    }
    //調用
    fun call(){
        SingleTonExample.instance.handleMessage("msg")
    }
}

同樣地,單例可以有超類的:

package com.talent.kotlin.example

object SingleTonExample:Functions("singleto") {

    fun handleMessage(msg:String){
        print("receive message:$msg")
    }
}

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

class Site {
    var name = "site"
    object DeskTop{
        var url = "www.runoob.com"
        fun showName(){
            print{"desk legs $name"} // 錯誤,不能訪問到外部類的方法和變量
        }
    }
}
fun main(args: Array<String>) {
    var site = Site()
    site.DeskTop.url // 錯誤,不能通過外部類的實例訪問到該對象
    Site.DeskTop.url // 正確
}

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

伴生對象

其實我們在擴展一節中,講到伴生對象擴展時就提到過“伴生對象”這個詞。

一個類里面只能聲明一個內部關聯對象,即關鍵字 companion 只能使用一次

伴生對象的成員看起來像其他語言的靜態成員,但在運行時他們仍然是真實對象的實例成員

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

如你所見,使用 companion 關鍵字來聲明伴生對象,其中伴生對象的名稱(上文中的“Factory”)是可以省略的,缺省情獎品下名稱為 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()
    }
}

這里我們同樣看一下,上述代碼在字節碼中變成了什么樣:

  public static abstract interface Factory<T>
  {
    public abstract T create();
  }
  
  public static final class MyClass
  {
    public static final Companion Companion = new Companion(null);
    
    public static final class Companion
      implements CompainC.Factory<CompainC.MyClass>
    {
      @NotNull
      public CompainC.MyClass create()
      {
        return new CompainC.MyClass();
      }
    }
  }

這里我們發現,Kotlin中好像沒有靜態方法或靜態屬性?實質上只是用創建一個靜態對象達到了類似的效果。

語義差異

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

  • 對象表達式是在使用他們的地方立即執行的
  • 對象聲明是在第一次被訪問到時延遲初始化的
  • 伴生對象的初始化是在相應的類被加載(解析)時,與 Java 靜態初始化器的語義相匹配
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容