Kotlin 語言學習(4) - 數據類、類委託 及 object 關鍵字

一、本文概要

 二、數據類和類委託

2.1 數據類:自動生成通用方法的默認實現

在平時的開發中,我們往往會使用許多的xxBean對象用作數據容器,而在定義這些對象時,一般會重寫它的以下三個方法:

  • equals:用來比較實例
  • hashCode:用來作爲例如HashMap這種基於哈希容器的類
  • toString:用來爲類生成按聲明順序排列的所有字段的字符串表達形式

Kotlin中,只需要爲你的類添加data關鍵字,以上這些必要的方法就會自動生成好,例如下面的例子,我們演示了以上三個方法的作用:

運行結果爲:

equalshashCode方法會將所有在主構造方法中聲明的屬性納入考慮:

  • equals方法會檢測所有的屬性的值是否相等
  • hashCode方法會返回一個根據所有屬性生成的哈希值

數據類和不可變性

在設計數據類時,應當儘量只使用只讀的屬性,讓數據類的實例不可變,因爲如果不這樣,被用作鍵的對象在加入HashMap或者類似容器後被修改了,容器會進入一種無效的狀態。

爲了讓使用不可變對象變得容易,Kotlin編譯器爲它們生成了copy方法,並在copy的同時修改某些屬性的值,copy出來的副本有着單獨的聲明週期而且不會影響代碼中引用原始實例的位置,使用方法如下:

2.2 類委託

當我們需要向一個類添加一些行爲時,一般有兩種做法:

  • 繼承這個類,在子類中增加方法:
    這種方法的缺點是:當系統不斷演進並且基類的實現被修改或者新方法被添加進去時,你做出的關於類的行爲的假設會失效。
  • 使用裝飾器模式:
    本質是創建一個新類,實現與原始類一樣的接口並將原來的類的實例作爲一個字段保存。與原始類擁有同樣行爲的方法不用修改,只需要直接轉發到原始類的實例。這種方法的缺點是:需要相當多的樣板代碼。

類的委託即一個類中定義的方法實際是調用另一個類的對象的方法來實現的。

以下實例中派生類 Derived 繼承了接口 Base 所有方法,並且委託一個傳入的 Base 類的對象來執行這些方法:

// 創建接口
interface Base {   
    fun print()
}

// 實現此接口的被委託的類
class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

// 通過關鍵字 by 建立委託類
class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // 輸出 10
}

在 Derived 聲明中,by 子句表示,將 b 保存在 Derived 的對象實例內部,而且編譯器將會生成繼承自 Base 接口的所有方法, 並將調用轉發給 b。

三、object 關鍵字

object關鍵字在多種情況下出現,它的核心理念爲:這個關鍵字 定義一個類並同時創建一個實例,下面我們介紹它的三個應用場景:

  • 對象聲明 :是定義單例的一種方式。
  • 伴生對象 :可以持有工廠方法和其它與這個類相關,但在調用時並不依賴類實例的方法,它們的成員可以通過類名來訪問。
  • 對象表達式: 用來替代Java的匿名內部類。

3.1 對象聲明:創建單例易如反掌

Java中,單例模式通常是使用private構造方法,並且用靜態字段來持有這個類僅有的實例。

而在Kotlin中,通過使用對象聲明功能,將類聲明與該類的單一實例聲明結合到了一起。

  • 對象聲明通過object關鍵字引入,一個對象聲明可以非常高效地以一句話來定義一個類和一個該類的變量。
  • 一個對象聲明可以包含屬性、方法、初始化語句塊等的聲明,但是 不允許聲明構造方法,這是因爲對象在定義的時候就已經創建了,不需要在其他地方調用構造方法。
  • 對象聲明允許使用對象名加.字符的方式來調用方法和訪問屬性。

繼承自接口的對象聲明

對象聲明可以繼承自類和接口,這通常在你使用的框架需要去實現一個接口,但是你的實現不包含任何狀態的時候很有用。

在類中聲明對象

在類中使用對象聲明時,這樣的對象同樣只有一個單一實例:它們在每個容器類的實例中具有相同的實例。

運行結果爲:

在 Java 中使用 Kotlin 對象

如果要在Java中使用Kotlin中的聲明對象,可以通過訪問靜態的INSTANCE字段:

3.2 伴生對象:工廠方法和靜態成員的地盤

Kotlin的類不能擁有靜態成員,作爲替代,Kotlin依賴包級別函數(在大多數情形下能夠替代Java的靜態方法)和對象聲明(在其他情況下替代Java的靜態方法,同時還包括靜態字段),在大多數情況下,推薦使用頂層函數,但是頂層函數不能訪問類的private變量。

因此,如果你需要寫一個 在沒有類實例的情況下 調用但是需要 訪問類內部的函數,可以將其寫成那個類中的 對象聲明的成員

在類中定義的對象之一可以使用一個特殊的關鍵字來標記 companion,如果這樣做,就獲得了直接 通過容器類名稱來訪問這個對象的方法和屬性的能力,不再需要顯示地指明對象的名稱,下面是一個基礎的示例:

運行的結果爲:

使用工廠方法創建對象

3.3 作爲普通對象使用的伴生對象

伴生對象是一個聲明在類中的普通對象,它可以有名字,實現一個接口或者有擴展函數或屬性。假設我們需要在對象和JSON之間進行序列化和反序列化,可以將序列化的邏輯放在伴生對象中。

運行結果爲:

在大多數情況下,通過包含伴生對象的類的名字(也就是例子中的Person類)來引用伴生對象,所以不必關心它的名字,如果省略了伴生對象的名字,默認的名字將會分配爲Companion

在伴生對象中實現接口

就像其它對象聲明一樣,伴生對象也可以實現接口,可以將包含它的類的名字當做實現了該接口的對象實例來使用。

運行結果爲:

伴生對象擴展

在 Kotlin 語言學習(2) - 函數的定義與調用 中,我們介紹了通過擴展函數,可以通過代碼庫中其它地方定義類實例調用的方法,但是如果你需要定義可以通過 類自身調用的方法,就像伴生對象方法或者Java靜態方法該怎麼辦呢?

舉例來說,如果類有一個伴生對象,可以通過在其上定義擴展函數來做到這一點,即類C有一個伴生對象,並且在C.Companion上定義了一個擴展函數func,則可以通過C.fun()來調用它。

下面,我們爲Person類的伴生對象定義一個擴展函數:

當調用toJson就像是它是一個伴生對象定義的方法一樣,但是實際上它是作爲擴展函數在外部定義的。而爲了能夠爲你的類定義擴展,必須在其中聲明一個對象,即使是空的。

3.4 對象表達式:改變寫法的匿名內部類

object關鍵字不僅能夠用來表明單例式的對象,還能用來聲明 匿名對象,它替代了Java中匿名內部類的用法。例如,讓我們來看看怎樣將一個典型的匿名內部類用法轉換成Kotlin

運行結果爲:

將匿名對象存儲到變量中

除了去掉對象的名字外,語法與對象聲明相同的。對象表達式聲明瞭一個類並創建了該類的一個實例,但是沒有給這個類或是實例分配一個名字。通常來說,它們都是不需要名字的,因爲你會將這個對象用作一個函數調用的參數。如果你需要給對象分配一個名字,可以將其存儲到一個變量中:

在對象表達式中修改變量的值

Java的匿名類一樣,在對象表達式中的代碼可以訪問創建它的函數中的變量,但是與Java不同,訪問並被限制在final變量,還可以在對象表達式中修改變量的值。

運行結果爲:

see you

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章