Kotlin 知識梳理系列文章
Kotlin 知識梳理(1) - Kotlin 基礎
Kotlin 知識梳理(2) - 函數的定義與調用
Kotlin 知識梳理(3) - 類、對象和接口
Kotlin 知識梳理(4) - 數據類、類委托 及 object 關鍵字
Kotlin 知識梳理(5) - lambda 表達式和成員引用
Kotlin 知識梳理(6) - Kotlin 的可空性
Kotlin 知識梳理(7) - Kotlin 的類型系統
Kotlin 知識梳理(8) - 運算符重載及其他約定
Kotlin 知識梳理(9) - 委托屬性
Kotlin 知識梳理(10) - 高階函數:Lambda 作為形參或返回值
Kotlin 知識梳理(11) - 內聯函數
Kotlin 知識梳理(12) - 泛型類型參數
一、本文概要
本文是對<<Kotlin in Action>>
的學習筆記,如果需要運行相應的代碼可以訪問在線環境 try.kotlinlang.org,這部分的思維導圖為:
二、數據類和類委托
2.1 數據類:自動生成通用方法的默認實現
在平時的開發中,我們往往會使用許多的xxBean
對象用作數據容器,而在定義這些對象時,一般會重寫它的以下三個方法:
-
equals
:用來比較實例 -
hashCode
:用來作為例如HashMap
這種基于哈希容器的類 -
toString
:用來為類生成按聲明順序排列的所有字段的字符串表達形式
在Kotlin
中,只需要為你的類添加data
關鍵字,以上這些必要的方法就會自動生成好,例如下面的例子,我們演示了以上三個方法的作用:
運行結果為:
equals
和hashCode
方法會將所有在主構造方法中聲明的屬性納入考慮:
-
equals
方法會檢測所有的屬性的值是否相等 -
hashCode
方法會返回一個根據所有屬性生成的哈希值
數據類和不可變性
在設計數據類時,應當盡量只使用只讀的屬性,讓數據類的實例不可變,因為如果不這樣,被用作鍵的對象在加入HashMap
或者類似容器后被修改了,容器會進入一種無效的狀態。
為了讓使用不可變對象變得容易,Kotlin
編譯器為它們生成了copy
方法,并在copy
的同時修改某些屬性的值,copy
出來的副本有著單獨的聲明周期而且不會影響代碼中引用原始實例的位置,使用方法如下:
2.2 類委托
當我們需要向一個類添加一些行為時,一般有兩種做法:
-
繼承這個類,在子類中增加方法
這種方法的缺點是:當系統不斷演進并且基類的實現被修改或者新方法被添加進去時,你做出的關于類的行為的假設會失效。 -
使用裝飾器模式
本質是創建一個新類,實現與原始類一樣的接口并將原來的類的實例作為一個字段保存。與原始類擁有同樣行為的方法不用修改,只需要直接轉發到原始類的實例。這種方法的缺點是:需要相當多的樣板代碼。
而Kotlin
將委托作為一個語言級別的功能做了頭等支持。無論什么時候實現一個接口,你都可以使用by
關鍵字 將接口的實現委托到另一個對象;當需要修改某些方法的行為時,可以重寫它們,這樣你的方法就會被調用而不是使用生成的方法,可以保留感到滿意的委托給內部的實例中的默認實現。
運行結果為:
三、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
變量,還可以在對象表達式中修改變量的值。
運行結果為:
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:http://www.lxweimin.com/p/fd82d18994ce
- 個人主頁:http://lizejun.cn
- 個人知識總結目錄:http://lizejun.cn/categories/