Kotlin 類、對象和接口(三)——編譯器生成的方法:數(shù)據(jù)類和類委托

Kotlin 類、對象和接口(一)——定義類繼承結(jié)構(gòu)
Kotlin 類煤率、對象和接口(一)——定義類繼承結(jié)構(gòu)

Java 平臺定義了一些需要在許多類中呈現(xiàn)的方法,并且通常是以一種很機械的方式實現(xiàn)墓律,譬如 equals钧嘶、hashCode 及 toString。幸運的是撰豺,Java IDE 可以將這些方法的生成自動化粪般,所以通常不需要手動書寫它們。這種情況下污桦,代碼庫就包含了樣板代碼亩歹。Kotlin 的編譯器領先了一步:它能將這些呆板的代碼生成放到母后,并不會因為自動生成的結(jié)果導致源代碼文件變得混亂。

通用對象方法

就像 Java 中的情況一樣捆憎,所有的 Kotlin 類也有許多可能需要重寫的方法:toString舅柜、equals 和 hashCode。先看一下一個簡單的用來存儲客戶名字和郵編的 Client 類躲惰。

// 代碼清單 3.1     Client 類的最初聲明
class Client(val name: String, val postalCode: Int)

字符串表示:toString()

Kotlin 中所有類同 Java 一樣致份,提供了一種方式來獲取類對象的字符串表示形式,默認來說础拨,一個對象的字符串表示形如 Client@5e9f23b4氮块,這并不是十分有用。想要改變它诡宗,需要重寫 toString 方法滔蝉。

// 代碼清單 3.2     為 Client 實現(xiàn) toString()
class Client(val name: String, val postalCode: Int) {
    override fun toString(): String = "Client(name='$name', postalCode=$postalCode)"
}

對象相等性:equals()

所有關(guān)于 Client 類的計算都發(fā)生在其外部,這個類只是用來存儲數(shù)據(jù)塔沃,這意味著簡單和透明蝠引。盡管如此,也許還會有一些針對這個類行為的需求蛀柴。例如螃概,假設想要將包好相同數(shù)據(jù)的對象視為相等:

>>> val client1 = Client("Alice", 342562)
>>> val client1 = Client("Alice", 342562)
// 在 kotlin 中,== 檢查對象是否相等鸽疾,而不是比較引用吊洼。這里會編譯成調(diào)用"equals"
>>> println(client1 == client2)
false

對象并不相等,意味著必須為 Client 類重寫 equals制肮。

==表示相等性
在 Java 中冒窍,可以使用 == 運算符來比較基本數(shù)據(jù)類型和引用類型。如果引用在基本數(shù)據(jù)類型上豺鼻,Java 的 == 比較的是值综液,然而在引用類型上 == 比較的是引用。因此拘领,在 Java 中意乓,眾所周知的實踐是總是調(diào)用 equals。
在 Kotlin 中约素,== 運算符是比較兩個對象的默認方式:本質(zhì)上說它就是通過調(diào)用 equals 來比較兩個值的届良。因此,如果 equals 在類中被重寫了圣猎,就能夠很安全地使用 == 來比較實例士葫。要想進行引用比較,可以使用 === 運算符送悔,這與 Java 中的 == 比較對象引用的效果一模一樣慢显。

修改后的 Client 類

// 代碼清單 3.3     為 Client 實現(xiàn) equals()
class Client(val name: String, val postalCode: Int) {

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as Client

        if (name != other.name) return false
        if (postalCode != other.postalCode) return false

        return true
    }
    override fun toString(): String = "Client(name='$name', postalCode=$postalCode)"
}

Hash 容器:hashCode()

hashCode 方法通常與 equals 一起重寫爪模。
創(chuàng)建一個元素的 set:一個名為 Alice 的客戶。接著荚藻,創(chuàng)建一個新的包含相同數(shù)據(jù)的 Client 實例并檢查它是否包含在 set 中屋灌。期望檢查會返回 true,因為這兩個實例是相等的应狱,但實際上返回的是 false:

>>> val processed = hashSetOf(Client("Alice", 342562))
>>> println(processed.contains(Client("Alice", 342562)))
false

原因是 Client 類缺少了 hashCode 方法共郭。因此它違反了通用的 hashCode 契約:如果兩個對象相等,他們必須有著相同的 hash 值疾呻。processed set 是一個 HashSet除嘹,在 HashSet 中值是以一種優(yōu)化過的方式來比較的:首先比較它們的 hash 值,然后只有當它們相等時才會去比較真正的值岸蜗。上面例子中 Client 類的兩個不同的實例有著不同的 hash 值尉咕,所以 set 認為它不包含第二個對象,即使 equals 會返回 true璃岳。
要修復這個問題年缎,可以向類中添加 hashCode 的實現(xiàn):

// 代碼清單 3.4     為 Client 實現(xiàn) hashCode()
class Client(val name: String, val postalCode: Int) {
    // ...
    override fun hashCode(): Int {
        var result = name.hashCode()
        result = 31 * result + postalCode
        return result
    }
}

現(xiàn)在這個類在所有的情況下都能按預期來工作了。


數(shù)據(jù)類:自動生成通用方法的實現(xiàn)

如果想要的類是一個方便的數(shù)據(jù)容器铃慷,需要重寫這些方法:toString晦款、equals 和 hasCode。通常來說這些方法的實現(xiàn)十分簡單枚冗,很多 IDE 也能夠自動生成它們,并確保它們的實現(xiàn)是正確且一致的蛇损。
不過赁温,在 Kotlin 中不必再去生成這些方法了。如果為類添加 data 修飾符淤齐,必要的方法將會自動生成好股囊。

// 代碼清單 3.5     數(shù)據(jù)類 Client
data class Client(val name: String, val postalCode: Int)

這樣就得到了一個重寫了所有標準 Java 的類:

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

equals 和 hashCode 方法會將所有在主構(gòu)造方法中聲明的屬性納入考慮。生成的 equals 方法會檢測所有的屬性值是否相等更啄。hashCode 方法會返回一個根據(jù)所有屬性生成的哈希值稚疹。沒有在主構(gòu)造方法中聲明的屬性將不會加入到相等性檢查和哈希值計算中去。

數(shù)據(jù)類和不可變性:copy() 方法

雖然數(shù)據(jù)類的屬性并沒有要求是 val祭务,同樣可以使用 var内狗,但還是強烈建議推薦只是用只讀屬性,讓數(shù)據(jù)類的實例不可變义锥。如果想使用這樣的實例作為 HashMap 或者類似容器的鍵柳沙,這會是必需的要求。因為如果不這樣拌倍,被用作鍵的對象在加入容器后被修改了赂鲤,容器可能會進入一種無效的狀態(tài)噪径。不可變對象同樣更容易理解,特別是在多線程代碼中:一旦一個對象被創(chuàng)建出來数初,他會一直保持初始狀態(tài)找爱,也不用擔心在代碼工作時其他線程修改了對象的值。
為了讓使用不可變對象的數(shù)據(jù)類變得更容易泡孩,Kotlin 編譯器為它們多生成了一個方法:一個允許 copy 類的實例的方法车摄,并在 copy 的同時修改某些屬性的值。創(chuàng)建副本通常是修改實例的好選擇:副本有著單獨的生命周期并且不會影響代碼中引用原始實例的位置珍德。


類委托:使用 "by" 關(guān)鍵字

設計大型面向?qū)ο笙到y(tǒng)的一個常見問題就是由繼承的實現(xiàn)導致的脆弱性练般。當擴展一個類并重寫某些方法是,代碼就變得依賴繼承的那個類的實現(xiàn)細節(jié)锈候。當系統(tǒng)不斷演進并且基類的實現(xiàn)被修改或者新方法被添加進去是薄料,做出的關(guān)于類行為的假設會失效,所以代碼也許最后就以不正確的行為而告終泵琳。
Kotlin 的設計就識別了這樣的問題摄职,并默認將類視為 final 的。這確保了只有那些設計成可擴展的類可以被繼承获列。當使用這樣的類時谷市,就會意識到它是開放的,就會注意這些修改需要與派生類兼容击孩。
但是常常需要向其他類添加一些行為迫悠,即使它沒有被設計為可擴展的。一個常用的實現(xiàn)方式以 裝飾器 模式文明巩梢。這種模式的本質(zhì)就是創(chuàng)建一個新類创泄,實現(xiàn)與原始類一樣的接口并將原來的類的實例作為一個字段保存。與原始類擁有同樣行為的方法不會被修改括蝠,只需要直接轉(zhuǎn)發(fā)到原始類的實例鞠抑。
這種方式的一個缺點是需要相當多的樣板代碼(像 Intellij IDEA 一樣的眾多 IDE 都有專門生成這樣代碼的功能)。Kotlin 將委托作為一個語言級別的功能做了頭等支持忌警。無論什么時候?qū)崿F(xiàn)一個接口搁拙,都可以使用 by 關(guān)鍵字將接口的實現(xiàn)委托到另一個對象。

class DelegatingCollection<T> (
    innerList: Collection<T> = ArrayList<T>()
) : Collection<T> by innerList {}

類中所有的方法都消失了法绵,編譯器會生成它們箕速。當需要修改某些方法的行為是,可以重寫它們礼烈,這樣方法就會被調(diào)用而不是使用生成的方法弧满。可以保留感到滿意的委托給內(nèi)部的實例中種默認實現(xiàn)此熬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末庭呜,一起剝皮案震驚了整個濱河市滑进,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌募谎,老刑警劉巖扶关,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異数冬,居然都是意外死亡节槐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門拐纱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铜异,“玉大人,你說我怎么就攤上這事秸架∽嶙” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵东抹,是天一觀的道長蚂子。 經(jīng)常有香客問我,道長缭黔,這世上最難降的妖魔是什么食茎? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮馏谨,結(jié)果婚禮上别渔,老公的妹妹穿的比我還像新娘。我一直安慰自己惧互,他們只是感情好钠糊,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著壹哺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪艘刚。 梳的紋絲不亂的頭發(fā)上管宵,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音攀甚,去河邊找鬼箩朴。 笑死,一個胖子當著我的面吹牛秋度,可吹牛的內(nèi)容都是我干的炸庞。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼荚斯,長吁一口氣:“原來是場噩夢啊……” “哼埠居!你這毒婦竟也來了查牌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤滥壕,失蹤者是張志新(化名)和其女友劉穎纸颜,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绎橘,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡胁孙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了称鳞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涮较。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖冈止,靈堂內(nèi)的尸體忽然破棺而出狂票,到底是詐尸還是另有隱情,我是刑警寧澤靶瘸,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布苫亦,位于F島的核電站,受9級特大地震影響怨咪,放射性物質(zhì)發(fā)生泄漏屋剑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一诗眨、第九天 我趴在偏房一處隱蔽的房頂上張望唉匾。 院中可真熱鬧,春花似錦匠楚、人聲如沸巍膘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽峡懈。三九已至,卻和暖如春与斤,著一層夾襖步出監(jiān)牢的瞬間肪康,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工撩穿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留磷支,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓食寡,卻偏偏與公主長得像雾狈,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抵皱,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Kotlin的類和接口與Java的類和接口是有一定的區(qū)別的。Kotlin的接口是可以包含屬性聲明锭弊。Kotlin默認...
    程自舟閱讀 10,338評論 0 11
  • 寫在開頭:本人打算開始寫一個Kotlin系列的教程味滞,一是使自己記憶和理解的更加深刻樱蛤,二是可以分享給同樣想學習Kot...
    胡奚冰閱讀 1,424評論 5 11
  • 前言 人生苦多,快來 Kotlin 剑鞍,快速學習Kotlin昨凡! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,212評論 9 118
  • 1. Java基礎部分 基礎部分的順序:基本語法蚁署,類相關(guān)的語法便脊,內(nèi)部類的語法,繼承相關(guān)的語法光戈,異常的語法哪痰,線程的語...
    子非魚_t_閱讀 31,643評論 18 399
  • 李小菲掩著雙耳坐在座位上筷弦,可耳后還是傳來“嗡嗡嗡”地議論聲肋演。她知道他們定是在討論她,她的背僵了又僵烂琴,無奈地走出教室...
    歐嘉言閱讀 7,075評論 272 274