六、Kotlin 類與對(duì)象

面向?qū)ο缶幊蹋∣OP)

在前面的章節(jié)中,我們學(xué)習(xí)了Kotlin的語(yǔ)言基礎(chǔ)知識(shí)、類型系統(tǒng)、集合類以及泛型相關(guān)的知識(shí)。在本章節(jié)以及下一章中,我們將一起來(lái)學(xué)習(xí)Kotlin對(duì)面向?qū)ο缶幊桃约昂瘮?shù)式編程的支持。

1 面向?qū)ο缶幊趟枷?/h2>

1.1 一切皆是映射

《易傳·系辭上傳》:“易有太極,是生兩儀,兩儀生四象,四象生八卦。” 如今的互聯(lián)網(wǎng)世界,其基石卻是01(陰陽(yáng)),不得不佩服我華夏先祖的博大精深的智慧。

一切皆是映射

計(jì)算機(jī)領(lǐng)域中的所有問(wèn)題,都可以通過(guò)向上一層進(jìn)行抽象封裝來(lái)解決.這里的封裝的本質(zhì)概念,其實(shí)就是“映射”。

就好比通過(guò)的電子電路中的電平進(jìn)行01邏輯映射,于是有了布爾代數(shù),數(shù)字邏輯電路系統(tǒng);

對(duì)01邏輯的進(jìn)一步封裝抽象成CPU指令集映射,誕生了匯編語(yǔ)言;

通過(guò)匯編語(yǔ)言的向上抽象一層編譯解釋器,于是有了pascal,fortran,C語(yǔ)言;
再對(duì)核心函數(shù)api進(jìn)行封裝形成開(kāi)發(fā)包(Development Kit), 于是有了Java,C++ 。

從面向過(guò)程到面向?qū)ο螅俚皆O(shè)計(jì)模式,架構(gòu)設(shè)計(jì),面向服務(wù),Sass/Pass/Iass等等的思想,各種軟件理論思想五花八門,但萬(wàn)變不離其宗——

  • 你要解決一個(gè)怎樣的問(wèn)題?
  • 你的問(wèn)題領(lǐng)域是怎樣的?
  • 你的模型(數(shù)據(jù)結(jié)構(gòu))是什么?
  • 你的算法是什么?
  • 你對(duì)這個(gè)世界的本質(zhì)認(rèn)知是怎樣的?
  • 你的業(yè)務(wù)領(lǐng)域的邏輯問(wèn)題,流程是什么?
    等等。

Grady Booch:我對(duì)OO編程的目標(biāo)從來(lái)就不是復(fù)用。相反,對(duì)我來(lái)說(shuō),對(duì)象提供了一種處理復(fù)雜性的方式。這個(gè)問(wèn)題可以追溯到亞里士多德:您把這個(gè)世界視為過(guò)程還是對(duì)象?在OO興起運(yùn)動(dòng)之前,編程以過(guò)程為中心--例如結(jié)構(gòu)化設(shè)計(jì)方法。然而,系統(tǒng)已經(jīng)到達(dá)了超越其處理能力的復(fù)雜性極點(diǎn)。有了對(duì)象,我們能夠通過(guò)提升抽象級(jí)別來(lái)構(gòu)建更大的、更復(fù)雜的系統(tǒng)--我認(rèn)為,這才是面向?qū)ο缶幊踢\(yùn)動(dòng)的真正勝利。

最初, 人們使用物理的或邏輯的二進(jìn)制機(jī)器指令來(lái)編寫(xiě)程序, 嘗試著表達(dá)思想中的邏輯, 控制硬件計(jì)算和顯示, 發(fā)現(xiàn)是可行的;

接著, 創(chuàng)造了助記符 —— 匯編語(yǔ)言, 比機(jī)器指令更容易記憶;

再接著, 創(chuàng)造了編譯器、解釋器和計(jì)算機(jī)高級(jí)語(yǔ)言, 能夠以人類友好自然的方式去編寫(xiě)程序, 在犧牲少量性能的情況下, 獲得比匯編語(yǔ)言更強(qiáng)且更容易使用的語(yǔ)句控制能力:條件、分支、循環(huán), 以及更多的語(yǔ)言特性: 指針、結(jié)構(gòu)體、聯(lián)合體、枚舉等, 還創(chuàng)造了函數(shù), 能夠?qū)⒁幌盗兄噶罘庋b成一個(gè)獨(dú)立的邏輯塊反復(fù)使用;

逐漸地,產(chǎn)生了面向過(guò)程的編程方法;

后來(lái), 人們發(fā)現(xiàn)將數(shù)據(jù)和邏輯封裝成對(duì)象, 更接近于現(xiàn)實(shí)世界, 且更容易維護(hù)大型軟件, 又出現(xiàn)了面向?qū)ο蟮木幊陶Z(yǔ)言和編程方法學(xué), 增加了新的語(yǔ)言特性: 繼承、 多態(tài)、 模板、 異常錯(cuò)誤。

為了不必重復(fù)開(kāi)發(fā)常見(jiàn)工具和任務(wù), 人們創(chuàng)造和封裝了容器及算法、SDK, 垃圾回收器, 甚至是并發(fā)庫(kù);

為了讓計(jì)算機(jī)語(yǔ)言更有力更有效率地表達(dá)各種現(xiàn)實(shí)邏輯, 消解軟件開(kāi)發(fā)中遇到的沖突, 還在語(yǔ)言中支持了元編程、 高階函數(shù), 閉包 等有用特性。

為了更高效率地開(kāi)發(fā)可靠的軟件和應(yīng)用程序, 人們逐漸構(gòu)建了代碼編輯器、 IDE、 代碼版本管理工具、公共庫(kù)、應(yīng)用框架、 可復(fù)用組件、系統(tǒng)規(guī)范、網(wǎng)絡(luò)協(xié)議、 語(yǔ)言標(biāo)準(zhǔn)等, 針對(duì)遇到的問(wèn)題提出了許多不同的思路和解決方案, 并總結(jié)提煉成特定的技術(shù)和設(shè)計(jì)模式, 還探討和形成了不少軟件開(kāi)發(fā)過(guò)程, 用來(lái)保證最終發(fā)布的軟件質(zhì)量。 盡管編寫(xiě)的這些軟件和工具還存在不少 BUG ,但是它們都“奇跡般地存活”, 并共同構(gòu)建了今天蔚為壯觀的互聯(lián)網(wǎng)時(shí)代的電商,互聯(lián)網(wǎng)金融,云計(jì)算,大數(shù)據(jù),物聯(lián)網(wǎng),機(jī)器智能等等的“虛擬世界”。

1.2 二進(jìn)制01與易經(jīng)陰陽(yáng)

二進(jìn)制數(shù)是用0和1兩個(gè)數(shù)碼來(lái)表示的數(shù)。它的基數(shù)為2,進(jìn)位規(guī)則是“逢二進(jìn)一”,借位規(guī)則是“借一當(dāng)二”,由18世紀(jì)德國(guó)數(shù)理哲學(xué)大師萊布尼茲發(fā)現(xiàn)。當(dāng)前的計(jì)算機(jī)系統(tǒng)使用的基本上是二進(jìn)制系統(tǒng)。

19世紀(jì)愛(ài)爾蘭邏輯學(xué)家B對(duì)邏輯命題的思考過(guò)程轉(zhuǎn)化為對(duì)符號(hào)0,1的某種代數(shù)演算,二進(jìn)制是逢2進(jìn)位的進(jìn)位制。0、1是基本算符。因?yàn)樗皇褂?、1兩個(gè)數(shù)字符號(hào),非常簡(jiǎn)單方便,易于用電子方式實(shí)現(xiàn)。

二進(jìn)制的發(fā)現(xiàn)直接導(dǎo)致了電子計(jì)算器和計(jì)算機(jī)的發(fā)明,并讓計(jì)算機(jī)得到了迅速的普及,進(jìn)入各行各業(yè),成為人類生活和生產(chǎn)的重要工具。

二進(jìn)制的實(shí)質(zhì)是通過(guò)兩個(gè)數(shù)字“0”和“1”來(lái)描述事件。在人類的生產(chǎn)、生活等許多領(lǐng)域,我們可以通過(guò)計(jì)算機(jī)來(lái)虛擬地描述現(xiàn)實(shí)中存在的事件,并能通過(guò)給定的條件和參數(shù)模擬事件變化的規(guī)律。二進(jìn)制的計(jì)算機(jī)幾乎是萬(wàn)能的,能將我們生活的現(xiàn)實(shí)世界完美復(fù)制,并且還能根據(jù)我們?nèi)祟惤o定的條件模擬在現(xiàn)實(shí)世界難以實(shí)現(xiàn)的各種實(shí)驗(yàn)。

但是,不論計(jì)算機(jī)能給我們?nèi)绾味嘧儭⑷绾瓮昝馈⑷绾螐?fù)雜的畫(huà)面,其本源只是簡(jiǎn)單的“0”和“1”。“0”和“1”在計(jì)算機(jī)中通過(guò)不同的組合與再組合,模擬出一個(gè)紛繁復(fù)雜、包羅萬(wàn)象的虛擬世界。我們簡(jiǎn)單圖示如下:

螢?zāi)豢煺?2017-07-01 10.38.22.png

二進(jìn)制的“0”和“1”通過(guò)計(jì)算機(jī)里能夠創(chuàng)造出一個(gè)虛擬的、紛繁的世界。自然界中的陰陽(yáng)形成了現(xiàn)實(shí)世界的萬(wàn)事萬(wàn)物。

所以自然世界的“陰”“陽(yáng)”作為基礎(chǔ)切實(shí)地造就了復(fù)雜的現(xiàn)實(shí)世界,計(jì)算機(jī)的“0”和“1”形象地模擬現(xiàn)實(shí)世界的一切現(xiàn)象,易學(xué)中的“卦”和“陰陽(yáng)爻”抽象地揭示了自然界存在的事件和其變化規(guī)律。

所以說(shuō),編程的本質(zhì)跟大自然創(chuàng)造萬(wàn)物的本質(zhì)是一樣的。

1.3 從面向過(guò)程到面向?qū)ο?/h3>

從IBM公司的約翰·巴庫(kù)斯在1957年開(kāi)發(fā)出世界上第一個(gè)高級(jí)程序設(shè)計(jì)語(yǔ)言Fortran至今,高級(jí)程序設(shè)計(jì)語(yǔ)言的發(fā)展已經(jīng)經(jīng)歷了整整半個(gè)世紀(jì)。在這期間,程序設(shè)計(jì)語(yǔ)言主要經(jīng)歷了從面向過(guò)程(如C和Pascal語(yǔ)言)到面向?qū)ο螅ㄈ鏑++和Java語(yǔ)言),再到面向組件編程(如.NET平臺(tái)下的C#語(yǔ)言),以及面向服務(wù)架構(gòu)技術(shù)(如SOA、Service以及最近很火的微服務(wù)架構(gòu))等。

面向過(guò)程編程

結(jié)構(gòu)化編程思想的核心:功能分解(自頂向下,逐層細(xì)化)。

1971年4月份的 Communications of ACM上,尼古拉斯·沃斯(Niklaus Wirth,1934年2月15日—, 結(jié)構(gòu)化編程思想的創(chuàng)始人。因發(fā)明了Euler、Alogo-W、Modula和Pascal等一系列優(yōu)秀的編程語(yǔ)言并提出了結(jié)構(gòu)化編程思想而在1984年獲得了圖靈獎(jiǎng)。)發(fā)表了論文“通過(guò)逐步求精方式開(kāi)發(fā)程序’(Program Development by Stepwise Refinement),首次提出“結(jié)構(gòu)化程序設(shè)計(jì)”(structure programming)的概念。

不要求一步就編制成可執(zhí)行的程序,而是分若干步進(jìn)行,逐步求精。

第一步編出的程序抽象度最高,第二步編出的程序抽象度有所降低…… 最后一步編出的程序即為可執(zhí)行的程序。

用這種方法編程,似乎復(fù)雜,實(shí)際上優(yōu)點(diǎn)很多,可使程序易讀、易寫(xiě)、易調(diào)試、易維護(hù)、易保證其正確性及驗(yàn)證其正確性。

結(jié)構(gòu)化程序設(shè)計(jì)方法又稱為“自頂向下”或“逐步求精”法,在程序設(shè)計(jì)領(lǐng)域引發(fā)了一場(chǎng)革命,成為程序開(kāi)發(fā)的一個(gè)標(biāo)準(zhǔn)方法,尤其是在后來(lái)發(fā)展起來(lái)的軟件工程中獲得廣泛應(yīng)用。有人評(píng)價(jià)說(shuō)Wirth的結(jié)構(gòu)化程序設(shè)計(jì)概念“完全改變了人們對(duì)程序設(shè)計(jì)的思維方式”,這是一點(diǎn)也不夸張的。

尼古拉斯· 沃思教授在編程界提出了一個(gè)著名的公式:

程序 = 數(shù)據(jù)結(jié)構(gòu) + 算法

面向?qū)ο缶幊?/h4>

面向?qū)ο缶幊趟枷氲暮诵模簯?yīng)對(duì)變化,提高復(fù)用。

阿倫·凱(Alan Kay):面向?qū)ο缶幊趟枷氲膭?chuàng)始人。2003年因在面向?qū)ο缶幊躺纤龅木薮筘暙I(xiàn)而獲得圖靈獎(jiǎng)。

The best way to predict the future is to invent it,預(yù)測(cè)未來(lái)最好的方法是創(chuàng)造它!(Alan Kay)

阿倫·凱是Smalltalk面向?qū)ο缶幊陶Z(yǔ)言的發(fā)明人之一,也是面向?qū)ο缶幊趟枷氲膭?chuàng)始人之一,同時(shí),他還是筆記本電腦最早的構(gòu)想者和現(xiàn)代Windows GUI的建筑師。最早提出PC概念和互聯(lián)網(wǎng)的也是阿倫·凱,所以人們都尊稱他為“預(yù)言大師”。他是當(dāng)今IT界屈指可數(shù)的技術(shù)天才級(jí)人物。

面向?qū)ο缶幊趟枷胫饕菑?fù)用性和靈活性(彈性)。復(fù)用性是面向?qū)ο缶幊痰囊粋€(gè)主要機(jī)制。靈活性主要是應(yīng)對(duì)變化的特性,因?yàn)榭蛻舻男枨笫遣粩喔淖兊模鯓舆m應(yīng)客戶需求的變化,這是軟件設(shè)計(jì)靈活性或者說(shuō)是彈性的問(wèn)題。

Java是一種面向?qū)ο缶幊陶Z(yǔ)言,它基于Smalltalk語(yǔ)言,作為OOP語(yǔ)言,它具有以下五個(gè)基本特性:

1.萬(wàn)物皆對(duì)象,每一個(gè)對(duì)象都會(huì)存儲(chǔ)數(shù)據(jù),并且可以對(duì)自身執(zhí)行操作。因此,每一個(gè)對(duì)象包含兩部分:成員變量和成員方法。在成員方法中可以改變成員變量的值。

2.程序是對(duì)象的集合,他們通過(guò)發(fā)送消息來(lái)告知彼此所要做的事情,也就是調(diào)用相應(yīng)的成員函數(shù)。

3.每一個(gè)對(duì)象都有自己的由其他對(duì)象所構(gòu)成的存儲(chǔ),也就是說(shuō)在創(chuàng)建新對(duì)象的時(shí)候可以在成員變量中使用已存在的對(duì)象。

4.每個(gè)對(duì)象都擁有其類型,每個(gè)對(duì)象都是某個(gè)類的一個(gè)實(shí)例,每一個(gè)類區(qū)別于其它類的特性就是可以向它發(fā)送什么類型的消息,也就是它定義了哪些成員函數(shù)。

5.某一個(gè)特定類型的所有對(duì)象都可以接受同樣的消息。另一種對(duì)對(duì)象的描述為:對(duì)象具有狀態(tài)(數(shù)據(jù),成員變量)、行為(操作,成員方法)和標(biāo)識(shí)(成員名,內(nèi)存地址)。

面向?qū)ο笳Z(yǔ)言其實(shí)是對(duì)現(xiàn)實(shí)生活中的實(shí)物的抽象。

每個(gè)對(duì)象能夠接受的請(qǐng)求(消息)由對(duì)象的接口所定義,而在程序中必須由滿足這些請(qǐng)求的代碼,這段代碼稱之為這個(gè)接口的實(shí)現(xiàn)。當(dāng)向某個(gè)對(duì)象發(fā)送消息(請(qǐng)求)時(shí),這個(gè)對(duì)象便知道該消息的目的(該方法的實(shí)現(xiàn)已定義),然后執(zhí)行相應(yīng)的代碼。

我們經(jīng)常說(shuō)一些代碼片段是優(yōu)雅的或美觀的,實(shí)際上意味著它們更容易被人類有限的思維所處理。

對(duì)于程序的復(fù)合而言,好的代碼是它的表面積要比體積增長(zhǎng)的慢。

代碼塊的“表面積”是是我們復(fù)合代碼塊時(shí)所需要的信息(接口API協(xié)議定義)。代碼塊的“體積”就是接口內(nèi)部的實(shí)現(xiàn)邏輯(API背后的實(shí)現(xiàn)代碼)。

在面向?qū)ο缶幊讨校粋€(gè)理想的對(duì)象應(yīng)該是只暴露它的抽象接口(純表面, 無(wú)體積),其方法則扮演箭頭的角色。如果為了理解一個(gè)對(duì)象如何與其他對(duì)象進(jìn)行復(fù)合,當(dāng)你發(fā)現(xiàn)不得不深入挖掘?qū)ο蟮膶?shí)現(xiàn)之時(shí),此時(shí)你所用的編程范式的原本優(yōu)勢(shì)就蕩然無(wú)存了。

面向組件和面向服務(wù)

  • 面向組件

我們知道面向?qū)ο笾С种赜茫侵赜玫膯卧苄。话闶穷悾欢嫦蚪M件則不同,它可以重用多個(gè)類甚至一個(gè)程序。也就是說(shuō)面向組件支持更大范圍內(nèi)的重用,開(kāi)發(fā)效率更高。如果把面向?qū)ο蟊茸髦赜昧慵敲疵嫦蚪M件則是重用部件。

  • 面向服務(wù)

將系統(tǒng)進(jìn)行功能化,每個(gè)功能提供一種服務(wù)。現(xiàn)在非常流行微服務(wù)MicroService技術(shù)以及SOA(面向服務(wù)架構(gòu))技術(shù)。

面向過(guò)程(Procedure)→面向?qū)ο螅∣bject)→ 面向組件(Component) →面向服務(wù)(Service)

正如解決數(shù)學(xué)問(wèn)題通常我們會(huì)談“思想”,諸如反證法、化繁為簡(jiǎn)等,解決計(jì)算機(jī)問(wèn)題也有很多非常出色的思想。思想之所以稱為思想,是因?yàn)椤八枷搿庇型卣剐耘c引導(dǎo)性,可以解決一系列問(wèn)題。

解決問(wèn)題的復(fù)雜程度直接取決于抽象的種類及質(zhì)量。過(guò)將結(jié)構(gòu)、性質(zhì)不同的底層實(shí)現(xiàn)進(jìn)行封裝,向上提供統(tǒng)一的API接口,讓使用者覺(jué)得就是在使用一個(gè)統(tǒng)一的資源,或者讓使用者覺(jué)得自己在使用一個(gè)本來(lái)底層不直接提供、“虛擬”出來(lái)的資源。

計(jì)算機(jī)中的所有問(wèn)題 , 都可以通過(guò)向上抽象封裝一層來(lái)解決。同樣的,任何復(fù)雜的問(wèn)題, 最終總能夠回歸最本質(zhì),最簡(jiǎn)單。

面向?qū)ο缶幊淌且环N自頂向下的程序設(shè)計(jì)方法。萬(wàn)事萬(wàn)物都是對(duì)象,對(duì)象有其行為(方法),狀態(tài)(成員變量,屬性)。OOP是一種編程思想,而不是針對(duì)某個(gè)語(yǔ)言而言的。當(dāng)然,語(yǔ)言影響思維方式,思維依賴語(yǔ)言的表達(dá),這也是辯證的來(lái)看。

所謂“面向?qū)ο笳Z(yǔ)言”,其實(shí)經(jīng)典的“過(guò)程式語(yǔ)言”(比如Pascal,C),也能體現(xiàn)面向?qū)ο蟮乃枷搿K^“類”和“對(duì)象”,就是C語(yǔ)言里面的抽象數(shù)據(jù)類型結(jié)構(gòu)體(struct)。

而面向?qū)ο蟮亩鄳B(tài)是唯一相比struct多付出的代價(jià),也是最重要的特性。這就是SmallTalk、Java這樣的面向?qū)ο笳Z(yǔ)言所提供的特性。

回到一個(gè)古老的話題:程序是什么?

在面向?qū)ο蟮木幊淌澜缋铮旅娴倪@個(gè)公式

程序 = 算法 + 數(shù)據(jù)結(jié)構(gòu)

可以簡(jiǎn)單重構(gòu)成:

程序 = 基于對(duì)象操作的算法 + 以對(duì)象為最小單位的數(shù)據(jù)結(jié)構(gòu)

封裝總是為了減少操作粒度,數(shù)據(jù)結(jié)構(gòu)上的封裝導(dǎo)致了數(shù)據(jù)的減少,自然減少了問(wèn)題求解的復(fù)雜度;對(duì)代碼的封裝使得代碼得以復(fù)用,減少了代碼的體積,同樣使問(wèn)題簡(jiǎn)化。這個(gè)時(shí)候,算法操作的就是一個(gè)抽象概念的集合。

在面向?qū)ο蟮某绦蛟O(shè)計(jì)中,我們便少不了集合類容器。容器就用來(lái)存放一類有共同抽象概念的東西。這里說(shuō)有共同概念的東西(而沒(méi)有說(shuō)對(duì)象),其實(shí),就是我們上一個(gè)章節(jié)中講到的泛型。這樣對(duì)于一個(gè)通用的算法,我們就可以最大化的實(shí)現(xiàn)復(fù)用,作用于的集合。

面向?qū)ο蟮谋举|(zhì)就是讓對(duì)象有多態(tài)性,把不同對(duì)象以同一特性來(lái)歸組,統(tǒng)一處理。至于所謂繼承、虛表、等等概念,只是其實(shí)現(xiàn)的細(xì)節(jié)。

在遵循這些面向?qū)ο笤O(shè)計(jì)原則基礎(chǔ)上,前輩們總結(jié)出一些解決不同問(wèn)題場(chǎng)景的設(shè)計(jì)模式,以GOF的23中設(shè)計(jì)模式最為知名。

我們用一幅圖簡(jiǎn)單概括一下面向?qū)ο缶幊痰闹R(shí)框架:

螢?zāi)豢煺?2017-07-01 11.29.07.png

講了這么多思考性的思想層面的東西,我們下面來(lái)開(kāi)始Kotlin的面向?qū)ο缶幊痰膶W(xué)習(xí)。Kotlin對(duì)面向?qū)ο缶幊淌峭耆С值摹?/p>

2 類與構(gòu)造函數(shù)

Kotlin和Java很相似,也是一種面向?qū)ο蟮恼Z(yǔ)言。下面我們來(lái)一起學(xué)習(xí)Kotlin的面向?qū)ο蟮奶匦浴H绻煜ava或者C++、C#中的類,您可以很快上手。同時(shí),您也將看到Kotlin與Java中的面向?qū)ο缶幊痰囊恍┎煌奶匦浴?/p>

Kotlin中的類和接口跟Java中對(duì)應(yīng)的概念有些不同,比如接口可以包含屬性聲明;Kotlin的類聲明,默認(rèn)是final和public的。

另外,嵌套類并不是默認(rèn)在內(nèi)部的。它們不包含外部類的隱式引用。

在構(gòu)造函數(shù)方面,Kotlin簡(jiǎn)短的主構(gòu)造函數(shù)在大多數(shù)情況下都可以滿足使用,當(dāng)然如果有稍微復(fù)雜的初始化邏輯,我們也可以聲明次級(jí)構(gòu)造函數(shù)來(lái)完成。

我們還可以使用 data 修飾符來(lái)聲明一個(gè)數(shù)據(jù)類,使用 object 關(guān)鍵字來(lái)表示單例對(duì)象、伴生對(duì)象等。

Kotlin類的成員可以包含:

  • 構(gòu)造函數(shù)和初始化塊
  • 屬性
  • 函數(shù)
  • 嵌套類和內(nèi)部類
  • 對(duì)象聲明

等。

2.1 聲明類

和大部分語(yǔ)言類似,Kotlin使用class作為類的關(guān)鍵字,當(dāng)我們聲明一個(gè)類時(shí),直接通過(guò)class加類名的方式來(lái)實(shí)現(xiàn):

class World

這樣我們就聲明了一個(gè)World類。

2.2 構(gòu)造函數(shù)

在 Kotlin 中,一個(gè)類可以有一個(gè)

  • 主構(gòu)造函數(shù)(primary constructor)和一個(gè)或多個(gè)
  • 次構(gòu)造函數(shù)(secondary constructor)。

主構(gòu)造函數(shù)

主構(gòu)造函數(shù)是類頭的一部分,直接放在類名后面:

open class Student constructor(var name: String, var age: Int) : Any() {
...
}

如果主構(gòu)造函數(shù)沒(méi)有任何注解或者可見(jiàn)性修飾符,可以省略這個(gè) constructor 關(guān)鍵字。如果構(gòu)造函數(shù)有注解或可見(jiàn)性修飾符,這個(gè) constructor 關(guān)鍵字是必需的,并且這些修飾符在它前面:


annotation class MyAutowired

class ElementaryStudent public @MyAutowired constructor(name: String, age: Int) : Student(name, age) {
...
}

與普通屬性一樣,主構(gòu)造函數(shù)中聲明的屬性可以是可變的(var)或只讀的(val)。

主構(gòu)造函數(shù)不能包含任何的代碼。初始化的代碼可以放到以 init 關(guān)鍵字作為前綴的初始化塊(initializer blocks)中:

open class Student constructor(var name: String, var age: Int) : Any() {

    init {
        println("Student{name=$name, age=$age} created!")
    }
   ...

}

主構(gòu)造的參數(shù)可以在初始化塊中使用,也可以在類體內(nèi)聲明的屬性初始化器中使用。

次構(gòu)造函數(shù)

在類體中,我們也可以聲明前綴有 constructor的次構(gòu)造函數(shù),次構(gòu)造函數(shù)不能有聲明 val 或 var :

class MiddleSchoolStudent {
    constructor(name: String, age: Int) {
    }
}

如果類有一個(gè)主構(gòu)造函數(shù),那么每個(gè)次構(gòu)造函數(shù)需要委托給主構(gòu)造函數(shù), 委托到同一個(gè)類的另一個(gè)構(gòu)造函數(shù)用 this 關(guān)鍵字即可:

class ElementarySchoolStudent public @MyAutowired constructor(name: String, age: Int) : Student(name, age) {
    override var weight: Float = 80.0f

    constructor(name: String, age: Int, weight: Float) : this(name, age) {
        this.weight = weight
    }

    ...
}

如果一個(gè)非抽象類沒(méi)有聲明任何(主或次)構(gòu)造函數(shù),它會(huì)有一個(gè)生成的不帶參數(shù)的主構(gòu)造函數(shù)。構(gòu)造函數(shù)的可見(jiàn)性是 public。

私有主構(gòu)造函數(shù)

我們?nèi)绻M@個(gè)構(gòu)造函數(shù)是私有的,我們可以如下聲明:

class DontCreateMe private constructor() {
}

這樣我們?cè)诖a中,就無(wú)法直接使用主構(gòu)造函數(shù)來(lái)實(shí)例化這個(gè)類,下面的寫(xiě)法是不允許的:

val dontCreateMe = DontCreateMe() // cannot access it

但是,我們可以通過(guò)次構(gòu)造函數(shù)引用這個(gè)私有主構(gòu)造函數(shù)來(lái)實(shí)例化對(duì)象:

2.2 類的屬性

我們?cè)俳o這個(gè)World類加入兩個(gè)屬性。我們可能直接簡(jiǎn)單地寫(xiě)成:

class World1 {
    val yin: Int
    val yang: Int
}

在Kotlin中,直接這樣寫(xiě)語(yǔ)法上是會(huì)報(bào)錯(cuò)的:

螢?zāi)豢煺?2017-07-02 01.22.34.png

意思很明顯,是說(shuō)這個(gè)類的屬性必須要初始化,或者如果不初始化那就得是抽象的abstract屬性。

我們把這兩個(gè)屬性都給初始化如下:

class World1 {
    val yin: Int = 0
    val yang: Int = 1
}

我們?cè)賮?lái)使用測(cè)試代碼來(lái)看下訪問(wèn)這兩個(gè)屬性的方式:

>>> class World1 {
...     val yin: Int = 0
...     val yang: Int = 1
... }
>>> val w1 = World1()
>>> w1.yin
0
>>> w1.yang
1

上面的World1類的代碼,在Java中等價(jià)的寫(xiě)法是:

public final class World1 {
   private final int yin;
   private final int yang = 1;

   public final int getYin() {
      return this.yin;
   }

   public final int getYang() {
      return this.yang;
   }
}

我們可以看出,Kotlin中的類的字段自動(dòng)帶有g(shù)etter方法和setter方法。而且寫(xiě)起來(lái)比Java要簡(jiǎn)潔的多。

2.3 函數(shù)(方法)

我們?cè)賮?lái)給這個(gè)World1類中加上一個(gè)函數(shù):

class World2 {
    val yin: Int = 0
    val yang: Int = 1

    fun plus(): Int {
        return yin + yang
    }
}


val w2 = World2()
println(w2.plus()) // 輸出 1

3 抽象類

3.1 抽象類的定義

含有抽象函數(shù)的類(這樣的類需要使用abstract修飾符來(lái)聲明),稱為抽象類。

下面是一個(gè)抽象類的例子:

abstract class Person(var name: String, var age: Int) : Any() {

    abstract var addr: String
    abstract val weight: Float

    abstract fun doEat()
    abstract fun doWalk()

    fun doSwim() {
        println("I am Swimming ... ")
    }

    open fun doSleep() {
        println("I am Sleeping ... ")
    }
}

3.2 抽象函數(shù)

在上面的這個(gè)抽象類中,不僅可以有抽象函數(shù)abstract fun doEat() abstract fun doWalk(),同時(shí)可以有具體實(shí)現(xiàn)的函數(shù)fun doSwim(), 這個(gè)函數(shù)默認(rèn)是final的。也就是說(shuō),我們不能重寫(xiě)這個(gè)doSwim函數(shù):

螢?zāi)豢煺?2017-07-02 14.02.27.png

如果一個(gè)函數(shù)想要設(shè)計(jì)成能被重寫(xiě),例如fun doSleep(),我們給它加上open關(guān)鍵字即可。然后,我們就可以在子類中重寫(xiě)這個(gè)open fun doSleep():

class Teacher(name: String, age: Int) : Person(name, age) {
    override var addr: String = "HangZhou"
    override val weight: Float = 100.0f

    override fun doEat() {
        println("Teacher is Eating ... ")
    }

    override fun doWalk() {
        println("Teacher is Walking ... ")
    }

    override fun doSleep() {
        super.doSleep()
        println("Teacher is Sleeping ... ")
    }

//    override fun doSwim() { // cannot be overriden
//        println("Teacher is Swimming ... ")
//    }
}

抽象函數(shù)是一種特殊的函數(shù):它只有聲明,而沒(méi)有具體的實(shí)現(xiàn)。抽象函數(shù)的聲明格式為:

abstract fun doEat()

關(guān)于抽象函數(shù)的特征,我們簡(jiǎn)單總結(jié)如下:

  • 抽象函數(shù)必須用abstract關(guān)鍵字進(jìn)行修飾
  • 抽象函數(shù)不用手動(dòng)添加open,默認(rèn)被open修飾
  • 抽象函數(shù)沒(méi)有具體的實(shí)現(xiàn)
  • 含有抽象函數(shù)的類成為抽象類,必須由abtract關(guān)鍵字修飾。抽象類中可以有具體實(shí)現(xiàn)的函數(shù),這樣的函數(shù)默認(rèn)是final(不能被覆蓋重寫(xiě)),如果想要重寫(xiě)這個(gè)函數(shù),給這個(gè)函數(shù)加上open關(guān)鍵字。

3.3 抽象屬性

抽象屬性就是在var或val前被abstract修飾,抽象屬性的聲明格式為:

abstract var addr : String
abstract val weight : Float

關(guān)于抽象屬性,需要注意的是:

  1. 抽象屬相在抽象類中不能被初始化
  2. 如果在子類中沒(méi)有主構(gòu)造函數(shù),要對(duì)抽象屬性手動(dòng)初始化。如果子類中有主構(gòu)造函數(shù),抽象屬性可以在主構(gòu)造函數(shù)中聲明。

綜上所述,抽象類和普通類的區(qū)別有:

1.抽象函數(shù)必須為public或者protected(因?yàn)槿绻麨閜rivate,則不能被子類繼承,子類便無(wú)法實(shí)現(xiàn)該方法),缺省情況下默認(rèn)為public。

也就是說(shuō),這三個(gè)函數(shù)

    abstract fun doEat()
    abstract fun doWalk()

    fun doSwim() {
        println("I am Swimming ... ")
    }

默認(rèn)的都是public的。

另外抽象類中的具體實(shí)現(xiàn)的函數(shù),默認(rèn)是final的。上面的三個(gè)函數(shù),等價(jià)的Java的代碼如下:

   public abstract void doEat();

   public abstract void doWalk();

   public final void doSwim() {
      String var1 = "I am Swimming ... ";
      System.out.println(var1);
   }

2.抽象類不能用來(lái)創(chuàng)建對(duì)象實(shí)例。也就是說(shuō),下面的寫(xiě)法編譯器是不允許的:

螢?zāi)豢煺?2017-07-02 13.24.58.png

3.如果一個(gè)類繼承于一個(gè)抽象類,則子類必須實(shí)現(xiàn)父類的抽象方法。實(shí)現(xiàn)父類抽象函數(shù),我們使用override關(guān)鍵字來(lái)表明是重寫(xiě)函數(shù):

class Programmer(override var addr: String, override val weight: Float, name: String, age: Int) : Person(name, age) {
    override fun doEat() {
        println("Programmer is Eating ... ")
    }

    override fun doWalk() {
        println("Programmer is Walking ... ")
    }
}

如果子類沒(méi)有實(shí)現(xiàn)父類的抽象函數(shù),則必須將子類也定義為為abstract類。例如:

abstract class Writer(override var addr: String, override val weight: Float, name: String, age: Int) : Person(name, age) {
    override fun doEat() {
        println("Programmer is Eating ... ")
    }

    abstract override fun doWalk();
}

doWalk函數(shù)沒(méi)有實(shí)現(xiàn)父類的抽象函數(shù),那么我們?cè)谧宇愔邪阉廊欢x為抽象函數(shù)。相應(yīng)地這個(gè)子類,也成為了抽象子類,需要使用abstract關(guān)鍵字來(lái)聲明。

如果抽象類中含有抽象屬性,再實(shí)現(xiàn)子類中必須將抽象屬性初始化,除非子類也為抽象類。例如我們聲明一個(gè)Teacher類繼承Person類:

class Teacher(name: String, age: Int) : Person(name, age) {
    override var addr: String //  error, 需要初始化,或者聲明為abstract
    override val weight: Float //  error, 需要初始化,或者聲明為abstract
    ...
}

這樣寫(xiě),編譯器會(huì)直接報(bào)錯(cuò):

螢?zāi)豢煺?2017-07-02 13.29.25.png

解決方法是,在實(shí)現(xiàn)的子類中,我們將抽象屬性初始化即可:

class Teacher(name: String, age: Int) : Person(name, age) {
    override var addr: String = "HangZhou"
    override val weight: Float = 100.0f

    override fun doEat() {
        println("Teacher is Eating ... ")
    }

    override fun doWalk() {
        println("Teacher is Walking ... ")
    }
}

4 接口

4.1 接口定義

和Java類似,Kotlin使用interface作為接口的關(guān)鍵詞:

interface ProjectService

Kotlin 的接口與 Java 8 的接口類似。與抽象類相比,他們都可以包含抽象的方法以及方法的實(shí)現(xiàn):

interface ProjectService {
    val name: String
    val owner: String
    fun save(project: Project)
    fun print() {
        println("I am project")
    }
}

4.2 實(shí)現(xiàn)接口

接口是沒(méi)有構(gòu)造函數(shù)的。我們使用冒號(hào): 語(yǔ)法來(lái)實(shí)現(xiàn)一個(gè)接口,如果有多個(gè)用逗號(hào)隔開(kāi):

class ProjectServiceImpl : ProjectService
class ProjectMilestoneServiceImpl : ProjectService, MilestoneService

我們也可以實(shí)現(xiàn)多個(gè)接口:

class Project

class Milestone

interface ProjectService {
    val name: String
    val owner: String
    fun save(project: Project)
    fun print() {
        println("I am project")
    }
}

interface MilestoneService {
    val name: String
    fun save(milestone: Milestone)
    fun print() {
        println("I am Milestone")
    }
}

class ProjectMilestoneServiceImpl : ProjectService, MilestoneService {
    override val name: String
        get() = "ProjectMilestone"
    override val owner: String
        get() = "Jack"

    override fun save(project: Project) {
        println("Save Project")
    }

    override fun print() {
//        super.print()
        super<ProjectService>.print()
        super<MilestoneService>.print()
    }

    override fun save(milestone: Milestone) {
        println("Save Milestone")
    }
}

當(dāng)子類繼承了某個(gè)類之后,便可以使用父類中的成員變量,但是并不是完全繼承父類的所有成員變量。具體的原則如下:

1.能夠繼承父類的public和protected成員變量;不能夠繼承父類的private成員變量;

2.對(duì)于父類的包訪問(wèn)權(quán)限成員變量,如果子類和父類在同一個(gè)包下,則子類能夠繼承;否則,子類不能夠繼承;

3.對(duì)于子類可以繼承的父類成員變量,如果在子類中出現(xiàn)了同名稱的成員變量,則會(huì)發(fā)生隱藏現(xiàn)象,即子類的成員變量會(huì)屏蔽掉父類的同名成員變量。如果要在子類中訪問(wèn)父類中同名成員變量,需要使用super關(guān)鍵字來(lái)進(jìn)行引用。

4.3 覆蓋沖突

在kotlin中, 實(shí)現(xiàn)繼承通常遵循如下規(guī)則:如果一個(gè)類從它的直接父類繼承了同一個(gè)函數(shù)的多個(gè)實(shí)現(xiàn),那么它必須重寫(xiě)這個(gè)函數(shù)并且提供自己的實(shí)現(xiàn)(或許只是直接用了繼承來(lái)的實(shí)現(xiàn)) 為表示使用父類中提供的方法我們用 super 表示。

在重寫(xiě)print()時(shí),因?yàn)槲覀儗?shí)現(xiàn)的ProjectService、MilestoneService都有一個(gè)print()函數(shù),當(dāng)我們直接使用super.print()時(shí),編譯器是無(wú)法知道我們想要調(diào)用的是那個(gè)里面的print函數(shù)的,這個(gè)我們叫做覆蓋沖突:

螢?zāi)豢煺?2017-07-02 15.36.20.png

這個(gè)時(shí)候,我們可以使用下面的語(yǔ)法來(lái)調(diào)用:

        super<ProjectService>.print()
        super<MilestoneService>.print()

4.4 接口中的屬性

在接口中聲明的屬性,可以是抽象的,或者是提供訪問(wèn)器的實(shí)現(xiàn)。

在企業(yè)應(yīng)用中,大多數(shù)的類型都是無(wú)狀態(tài)的,如:Controller、ApplicationService、DomainService、Repository等。

因?yàn)榻涌跊](méi)有狀態(tài), 所以它的屬性是無(wú)狀態(tài)的。


interface MilestoneService {
    val name: String // 抽象的
    val owner: String get() = "Jack" // 訪問(wèn)器

    fun save(milestone: Milestone)
    fun print() {
        println("I am Milestone")
    }
}


class MilestoneServiceImpl : MilestoneService {
    override val name: String
        get() = "MilestoneServiceImpl name"
    

    override fun save(milestone: Milestone) {
        println("save Milestone")
    }
}

5 抽象類和接口的差異

概念上的區(qū)別

接口主要是對(duì)動(dòng)作的抽象,定義了行為特性的規(guī)約。
抽象類是對(duì)根源的抽象。當(dāng)你關(guān)注一個(gè)事物的本質(zhì)的時(shí)候,用抽象類;當(dāng)你關(guān)注一個(gè)操作的時(shí)候,用接口。

語(yǔ)法層面上的區(qū)別

接口不能保存狀態(tài),可以有屬性但必須是抽象的。
一個(gè)類只能繼承一個(gè)抽象類,而一個(gè)類卻可以實(shí)現(xiàn)多個(gè)接口。

類如果要實(shí)現(xiàn)一個(gè)接口,它必須要實(shí)現(xiàn)接口聲明的所有方法。但是,類可以不實(shí)現(xiàn)抽象類聲明的所有方法,當(dāng)然,在這種情況下,類也必須得聲明成是抽象的。

接口中所有的方法隱含的都是抽象的。而抽象類則可以同時(shí)包含抽象和非抽象的方法。

設(shè)計(jì)層面上的區(qū)別

抽象類是對(duì)一種事物的抽象,即對(duì)類抽象,而接口是對(duì)行為的抽象。抽象類是對(duì)整個(gè)類整體進(jìn)行抽象,包括屬性、行為,但是接口卻是對(duì)類局部(行為)進(jìn)行抽象。

繼承是 is a的關(guān)系,而 接口實(shí)現(xiàn)則是 has a 的關(guān)系。如果一個(gè)類繼承了某個(gè)抽象類,則子類必定是抽象類的種類,而接口實(shí)現(xiàn)就不需要有這層類型關(guān)系。

設(shè)計(jì)層面不同,抽象類作為很多子類的父類,它是一種模板式設(shè)計(jì)。而接口是一種行為規(guī)范,它是一種輻射式設(shè)計(jì)。也就是說(shuō):

  • 對(duì)于抽象類,如果需要添加新的方法,可以直接在抽象類中添加具體的實(shí)現(xiàn),子類可以不進(jìn)行變更;

  • 而對(duì)于接口則不行,如果接口進(jìn)行了變更,則所有實(shí)現(xiàn)這個(gè)接口的類都必須進(jìn)行相應(yīng)的改動(dòng)。

實(shí)際應(yīng)用上的差異

在實(shí)際使用中,使用抽象類(也就是繼承),是一種強(qiáng)耦合的設(shè)計(jì),用來(lái)描述A is a B 的關(guān)系,即如果說(shuō)A繼承于B,那么在代碼中將A當(dāng)做B去使用應(yīng)該完全沒(méi)有問(wèn)題。比如在Android中,各種控件都可以被當(dāng)做View去處理。

如果在你設(shè)計(jì)中有兩個(gè)類型的關(guān)系并不是is a,而是is like a,那就必須慎重考慮繼承。因?yàn)橐坏┪覀兪褂昧死^承,就要小心處理好子類跟父類的耦合依賴關(guān)系。組合優(yōu)于繼承。

6 繼承

繼承是面向?qū)ο缶幊痰囊粋€(gè)重要的方式,因?yàn)橥ㄟ^(guò)繼承,子類就可以擴(kuò)展父類的功能。

在Kotlin中,所有的類會(huì)默認(rèn)繼承Any這個(gè)父類,但Any并不完全等同于java中的Object類,因?yàn)樗挥衑quals(),hashCode()和toString()這三個(gè)方法。

6.1 open類

除了抽象類、接口默認(rèn)可以被繼承(實(shí)現(xiàn))外,我們也可以把一個(gè)類聲明為open的,這樣我們就可以繼承這個(gè)open類。

當(dāng)我們想定義一個(gè)父類時(shí),需要使用open關(guān)鍵字:

open class Base{ 
} 

當(dāng)然,抽象類是默認(rèn)open的。

然后在子類中使用冒號(hào)進(jìn)行繼承

class SubClass : Base(){ 
} 

如果父類有構(gòu)造函數(shù),那么必須在子類的主構(gòu)造函數(shù)中進(jìn)行繼承,沒(méi)有的話則可以選擇主構(gòu)造函數(shù)或二級(jí)構(gòu)造函數(shù)

//父類 
open class Base(type:String){ 
  
} 
  
//子類 
class SubClass(type:String) : Base(type){ 
  
} 

Kotlin中的override重寫(xiě)和java中也有所不同,因?yàn)镵otlin提倡所有的操作都是明確的,因此需要將希望被重寫(xiě)的函數(shù)設(shè)為open:

open fun doSomething() {} 

然后通過(guò)override標(biāo)記實(shí)現(xiàn)重寫(xiě)

override fun doSomething() { 
 super.doSomething() 
 } 

同樣的,抽象函數(shù)以及接口中定義的函數(shù)默認(rèn)都是open的。

override重寫(xiě)的函數(shù)也是open的,如果希望它不被重寫(xiě),可以在前面增加final :

open class SubClass : Base{ 
 constructor(type:String) : super(type){ 
 } 
  
 final override fun doSomething() { 
 super.doSomething() 
 } 
} 

6.2 多重繼承

有些編程語(yǔ)言支持一個(gè)類擁有多個(gè)父類,例如C++。 我們將這個(gè)特性稱之為多重繼承(multiple inheritance)。多重繼承會(huì)有二義性和鉆石型繼承樹(shù)(DOD:Diamond Of Death)的復(fù)雜性問(wèn)題。Kotlin跟Java一樣,沒(méi)有采用多繼承,任何一個(gè)子類僅允許一個(gè)父類存在,而在多繼承的問(wèn)題場(chǎng)景下,使用實(shí)現(xiàn)多個(gè)interface 組合的方式來(lái)實(shí)現(xiàn)多繼承的功能。

代碼示例:

package com.easy.kotlin

/**
 * Created by jack on 2017/7/2.
 */

abstract class Animal {
    fun doEat() {
        println("Animal Eating")
    }
}


abstract class Plant {
    fun doEat() {
        println("Plant Eating")
    }
}


interface Runnable {
    fun doRun()
}

interface Flyable {
    fun doFly()
}

class Dog : Animal(), Runnable {
    override fun doRun() {
        println("Dog Running")
    }
}

class Eagle : Animal(), Flyable {
    override fun doFly() {
        println("Eagle Flying")
    }
}

// 始祖鳥(niǎo), 能飛也能跑
class Archaeopteryx : Animal(), Runnable, Flyable {
    override fun doRun() {
        println("Archaeopteryx Running")
    }

    override fun doFly() {
        println("Archaeopteryx Flying")
    }

}

fun main(args: Array<String>) {
    val d = Dog()
    d.doEat()
    d.doRun()

    val e = Eagle()
    e.doEat()
    e.doFly()

    val a = Archaeopteryx()
    a.doEat()
    a.doFly()
    a.doRun()
}



上述代碼類之間的關(guān)系,我們用圖示如下:

螢?zāi)豢煺?2017-07-02 22.30.59.png

我們可以看出,Archaeopteryx繼承了Animal類,用了父類doEat()函數(shù)功能;實(shí)現(xiàn)了Runnable接口,擁有了doRun()函數(shù)規(guī)范;實(shí)現(xiàn)了Flyable接口,擁有了doFly()函數(shù)規(guī)范。

在這里,我們通過(guò)實(shí)現(xiàn)多個(gè)接口,組合完成了的多個(gè)功能,而不是設(shè)計(jì)多個(gè)層次的復(fù)雜的繼承關(guān)系。

7 枚舉類

Kotlin的枚舉類定義如下:

public abstract class Enum<E : Enum<E>>(name: String, ordinal: Int): Comparable<E> {
    companion object {}

    public final val name: String
    public final val ordinal: Int

    public override final fun compareTo(other: E): Int
    protected final fun clone(): Any

    public override final fun equals(other: Any?): Boolean
    public override final fun hashCode(): Int
    public override fun toString(): String
}

我們可以看出,這個(gè)枚舉類有兩個(gè)屬性:

    public final val name: String
    public final val ordinal: Int

分別表示的是枚舉對(duì)象的值跟下標(biāo)位置。

同時(shí),我們可以看出枚舉類還實(shí)現(xiàn)了Comparable<E>接口。

7.1 枚舉類基本用法

枚舉類的最基本的用法是實(shí)現(xiàn)類型安全的枚舉:

enum class Direction {
    NORTH, SOUTH, WEST, EAST
}

>>> val north = Direction.NORTH
>>> north.name
NORTH
>>> north.ordinal
0
>>> north is Direction
true

每個(gè)枚舉常量都是一個(gè)對(duì)象。枚舉常量用逗號(hào)分隔。

7.2 初始化枚舉值

我們可以如下初始化枚舉類的值:

enum class Color(val rgb: Int) {
        RED(0xFF0000),
        GREEN(0x00FF00),
        BLUE(0x0000FF)
}

>>> val red = Color.RED
>>> red.rgb
16711680


另外,枚舉常量也可以聲明自己的匿名類:


enum class ActivtyLifeState {
    onCreate {
        override fun signal() = onStart
    },

    onStart {
        override fun signal() = onStop
    },

    onStop {
        override fun signal() = onStart
    },

    onDestroy {
        override fun signal() = onDestroy
    };

    abstract fun signal(): ActivtyLifeState
}

>>> val s = ActivtyLifeState.onCreate
>>> println(s.signal())
onStart

7.3 使用枚舉常量

我們使用enumValues()函數(shù)來(lái)列出枚舉的所有值:

@SinceKotlin("1.1")
public inline fun <reified T : Enum<T>> enumValues(): Array<T>

每個(gè)枚舉常量,默認(rèn)都name名稱和ordinal位置的屬性(這個(gè)跟Java的Enum類里面的類似):

val name: String
val ordinal: Int

代碼示例:

enum class RGB { RED, GREEN, BLUE }

>>> val rgbs = enumValues<RGB>().joinToString { "${it.name} : ${it.ordinal} " }
>>> rgbs
RED : 0 , GREEN : 1 , BLUE : 2 

我們直接聲明了一個(gè)簡(jiǎn)單枚舉類,我們使用遍歷函數(shù)enumValues<RGB>()列出了RGB枚舉類的所有枚舉值。使用it.name it.ordinal直接訪問(wèn)各個(gè)枚舉值的名稱和位置。

另外,我們也可以自定義枚舉屬性值:

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

>>> val colors = enumValues<Color>().joinToString { "${it.rgb} : ${it.name} : ${it.ordinal} " }
>>> colors
16711680 : RED : 0 , 65280 : GREEN : 1 , 255 : BLUE : 2 

然后,我們可以直接使用it.rgb訪問(wèn)屬性名來(lái)得到對(duì)應(yīng)的屬性值。

8 注解類

Kotlin 的注解與 Java 的注解完全兼容。

8.1 聲明注解

annotation class 注解名

代碼示例:


@Target(AnnotationTarget.CLASS,
        AnnotationTarget.FUNCTION,
        AnnotationTarget.EXPRESSION,
        AnnotationTarget.FIELD,
        AnnotationTarget.LOCAL_VARIABLE,
        AnnotationTarget.TYPE,
        AnnotationTarget.TYPEALIAS,
        AnnotationTarget.TYPE_PARAMETER,
        AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@Repeatable
annotation class MagicClass


@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@Repeatable
annotation class MagicFunction


@Target(AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@Repeatable
annotation class MagicConstructor

在上面的代碼中,我們通過(guò)向注解類添加元注解(meta-annotation)的方法來(lái)指定其他屬性:

  • @Target : 指定這個(gè)注解可被用于哪些元素(類, 函數(shù), 屬性, 表達(dá)式, 等等.);
  • @Retention : 指定這個(gè)注解的信息是否被保存到編譯后的 class 文件中, 以及在運(yùn)行時(shí)是否可以通過(guò)反
    射訪問(wèn)到它;
  • @Repeatable: 允許在單個(gè)元素上多次使用同一個(gè)注解;
  • @MustBeDocumented : 表示這個(gè)注解是公開(kāi) API 的一部分, 在自動(dòng)產(chǎn)生的 API 文檔的類或者函數(shù)簽名中, 應(yīng)該包含這個(gè)注解的信息。

這幾個(gè)注解定義在kotlin/annotation/Annotations.kt類中。

7.8.2 使用注解

注解可以用在類、函數(shù)、參數(shù)、變量(成員變量、局部變量)、表達(dá)式、類型上等。這個(gè)由該注解的元注解@Target定義。

@MagicClass class Foo @MagicConstructor constructor() {

    constructor(index: Int) : this() {
        this.index = index
    }

    @MagicClass var index: Int = 0
    @MagicFunction fun magic(@MagicClass name: String) {

    }
}

注解在主構(gòu)造器上,主構(gòu)造器必須加上關(guān)鍵字 “constructor”

@MagicClass class Foo @MagicConstructor constructor() {
...
}

9 單例模式(Singleton)與伴生對(duì)象(companion object)

9.1 單例模式(Singleton)

單例模式很常用。它是一種常用的軟件設(shè)計(jì)模式。例如,Spring中的Bean默認(rèn)就是單例。通過(guò)單例模式可以保證系統(tǒng)中一個(gè)類只有一個(gè)實(shí)例。即一個(gè)類只有一個(gè)對(duì)象實(shí)例。

我們用Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的單例類的代碼如下:

class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

測(cè)試代碼:

Singleton singleton1 = Singleton.getInstance();

可以看出,我們先在單例類中聲明了一個(gè)私有靜態(tài)的Singleton instance變量,然后聲明一個(gè)私有構(gòu)造函數(shù)private Singleton() {}, 這個(gè)私有構(gòu)造函數(shù)使得外部無(wú)法直接通過(guò)new的方式來(lái)構(gòu)建對(duì)象:

Singleton singleton2 = new Singleton(); //error, cannot private access

最后提供一個(gè)public的獲取當(dāng)前類的唯一實(shí)例的靜態(tài)方法getInstance()。我們這里給出的是一個(gè)簡(jiǎn)單的單例類,是線程不安全的。

9.2 object對(duì)象

Kotlin中沒(méi)有 靜態(tài)屬性和方法,但是也提供了實(shí)現(xiàn)類似于單例的功能,我們可以使用關(guān)鍵字 object 聲明一個(gè)object對(duì)象:

object AdminUser {
    val username: String = "admin"
    val password: String = "admin"
    fun getTimestamp() = SimpleDateFormat("yyyyMMddHHmmss").format(Date())
    fun md5Password() = EncoderByMd5(password + getTimestamp())
}

測(cè)試代碼:


    val adminUser = AdminUser.username
    val adminPassword = AdminUser.md5Password()
    println(adminUser)  // admin
    println(adminPassword)  // g+0yLfaPVYxUf6TMIdXFXw==,這個(gè)值具體運(yùn)行時(shí)會(huì)變化

為了方便在REPL中演示說(shuō)明,我們?cè)賹?xiě)一個(gè)示例代碼:

>>> object User {
...     val username: String = "admin"
...     val password: String = "admin"
... }

object對(duì)象只能通過(guò)對(duì)象名字來(lái)訪問(wèn):

>>> User.username
admin
>>> User.password
admin

不能像下面這樣使用構(gòu)造函數(shù):

>>> val u = User()
error: expression 'User' of type 'Line130.User' cannot be invoked as a function. The function 'invoke()' is not found
val u = User()
        ^

為了更加直觀的了解object對(duì)象的概念,我們把上面的object User的代碼反編譯成Java代碼:

public final class User {
   @NotNull
   private static final String username = "admin";
   @NotNull
   private static final String password = "admin";
   public static final User INSTANCE;

   @NotNull
   public final String getUsername() {
      return username;
   }

   @NotNull
   public final String getPassword() {
      return password;
   }

   private User() {
      INSTANCE = (User)this;
      username = "admin";
      password = "admin";
   }

   static {
      new User();
   }
}

從上面的反編譯代碼,我們可以直觀了解Kotlin的object背后的一些原理。

9.3 嵌套(Nested)object對(duì)象

這個(gè)object對(duì)象還可以放到一個(gè)類里面:


class DataProcessor {
    fun process() {
        println("Process Data")
    }


    object FileUtils {
        val userHome = "/Users/jack/"

        fun getFileContent(file: String): String {
            var content = ""
            val f = File(file)
            f.forEachLine { content = content + it + "\n" }
            return content
        }

    }
}


測(cè)試代碼:

DataProcessor.FileUtils.userHome // /Users/jack/
DataProcessor.FileUtils.getFileContent("test.data") // 輸出文件的內(nèi)容

同樣的,我們只能通過(guò)類的名稱來(lái)直接訪問(wèn)object,不能使用對(duì)象實(shí)例引用。下面的寫(xiě)法是錯(cuò)誤的:

val dp = DataProcessor()
dp.FileUtils.userHome // error, Nested object FileUtils cannot access object via reference

我們?cè)贘ava中通常會(huì)寫(xiě)一些Utils類,這樣的類我們?cè)贙otlin中就可以直接使用object對(duì)象:


object HttpUtils {
    val client = OkHttpClient()

    @Throws(Exception::class)
    fun getSync(url: String): String? {
        val request = Request.Builder()
                .url(url)
                .build()

        val response = client.newCall(request).execute()
        if (!response.isSuccessful()) throw IOException("Unexpected code " + response)

        val responseHeaders = response.headers()
        for (i in 0..responseHeaders.size() - 1) {
            println(responseHeaders.name(i) + ": " + responseHeaders.value(i))
        }
        return response.body()?.string()
    }

    @Throws(Exception::class)
    fun getAsync(url: String) {
        var result: String? = ""

        val request = Request.Builder()
                .url(url)
                .build()
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException?) {
                e?.printStackTrace()
            }

            @Throws(IOException::class)
            override fun onResponse(call: Call, response: Response) {
                if (!response.isSuccessful()) throw IOException("Unexpected code " + response)

                val responseHeaders = response.headers()
                for (i in 0..responseHeaders.size() - 1) {
                    println(responseHeaders.name(i) + ": " + responseHeaders.value(i))
                }
                result = response.body()?.string()
                println(result)

            }
        })
    }
}

測(cè)試代碼:

    val url = "http://www.baidu.com"
    val html1 = HttpUtils.getSync(url) // 同步get
    println("html1=${html1}") 
    HttpUtils.getAsync(url) // 異步get

9.4 匿名object

還有,在代碼行內(nèi),有時(shí)候我們需要的僅僅是一個(gè)簡(jiǎn)單的對(duì)象,我們這個(gè)時(shí)候就可以使用下面的匿名object的方式:

fun distance(x: Double, y: Double): Double {
    val porigin = object {
        var x = 0.0
        var y = 0.0
    }
    return Math.sqrt((x - porigin.x) * (x - porigin.x) + (y - porigin.y) * (y - porigin.y))
}

測(cè)試代碼:

distance(3.0, 4.0)

需要注意的是,匿名對(duì)象只可以用在本地和私有作用域中聲明的類型。代碼示例:

class AnonymousObjectType {
    // 私有函數(shù),返回的是匿名object類型
    private fun privateFoo() = object {
        val x: String = "x"
    }

    // 公有函數(shù),返回的類型是 Any
    fun publicFoo() = object {
        val x: String = "x" // 無(wú)法訪問(wèn)到
    }

    fun test() {
        val x1 = privateFoo().x        // Works
        //val x2 = publicFoo().x  // ERROR: Unresolved reference 'x'
    }
}


fun main(args: Array<String>) {
    AnonymousObjectType().publicFoo().x // Unresolved reference 'x'
}

跟 Java 匿名內(nèi)部類類似,object對(duì)象表達(dá)式中的代碼可以訪問(wèn)來(lái)自包含它的作用域的變量(與 Java 不同的是,這不限于 final 變量):

fun countCompare() {
    var list = mutableListOf(1, 4, 3, 7, 11, 9, 10, 20)
    var countCompare = 0
    Collections.sort(list, object : Comparator<Int> {
        override fun compare(o1: Int, o2: Int): Int {
            countCompare++
            println("countCompare=$countCompare")
            println(list)
            return o1.compareTo(o2)
        }
    })
}

測(cè)試代碼:

countCompare()

countCompare=1
[1, 4, 3, 7, 11, 9, 10, 20]
...
countCompare=17
[1, 3, 4, 7, 9, 10, 11, 20]

9.5 伴生對(duì)象(companion object)

Kotlin中還提供了 伴生對(duì)象 ,用companion object關(guān)鍵字聲明:

class DataProcessor {
    fun process() {
        println("Process Data")
    }


    object FileUtils {
        val userHome = "/Users/jack/"

        fun getFileContent(file: String): String {
            var content = ""
            val f = File(file)
            f.forEachLine { content = content + it + "\n" }
            return content
        }

    }

    companion object StringUtils {
        fun isEmpty(s: String): Boolean {
            return s.isEmpty()
        }
    }

}

一個(gè)類只能有1個(gè)伴生對(duì)象。也就是是下面的寫(xiě)法是錯(cuò)誤的:

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

    companion object Factory2 { // error, only 1 companion object is allowed per class
        fun create(): MyClass = MyClass()
    }
}

一個(gè)類的伴生對(duì)象默認(rèn)引用名是Companion:

class ClassB {
    companion object {
        fun create(): ClassB = ClassB()
        fun get() = "Hi, I am CompanyB"
    }
}

我們可以直接像在Java靜態(tài)類中使用靜態(tài)方法一樣使用一個(gè)類的伴生對(duì)象的函數(shù),屬性(但是在運(yùn)行時(shí),它們依舊是實(shí)體的實(shí)例成員):

    ClassB.Companion.index
    ClassB.Companion.create()
    ClassB.Companion.get()

其中, Companion可以省略不寫(xiě):

    ClassB.index
    ClassB.create()
    ClassB.get()

當(dāng)然,我們也可以指定伴生對(duì)象的名稱:

class ClassC {
    var index = 0
    fun get(index: Int): Int {
        return 0
    }

    companion object CompanyC {
        fun create(): ClassC = ClassC()
        fun get() = "Hi, I am CompanyC"
    }
}

測(cè)試代碼:

    ClassC.index
    ClassC.create()// com.easy.kotli.ClassC@7440e464,具體運(yùn)行值會(huì)變化
    ClassC.get() // Hi, I am CompanyC
    ClassC.CompanyC.index
    ClassC.CompanyC.create()
    ClassC.CompanyC.get()

伴生對(duì)象的初始化是在相應(yīng)的類被加載解析時(shí),與 Java 靜態(tài)初始化器的語(yǔ)義相匹配。

即使伴生對(duì)象的成員看起來(lái)像其他語(yǔ)言的靜態(tài)成員,在運(yùn)行時(shí)他們?nèi)匀皇钦鎸?shí)對(duì)象的實(shí)例成員。而且,還可以實(shí)現(xiàn)接口:

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


class MyClass {
    companion object : BeanFactory<MyClass> {
        override fun create(): MyClass {
            println("MyClass Created!")
            return MyClass()
        }
    }
}

測(cè)試代碼:

    MyClass.create()  // "MyClass Created!"
    MyClass.Companion.create() // "MyClass Created!"


另外,如果想使用Java中的靜態(tài)成員和靜態(tài)方法的話,我們可以用:

@JvmField注解:生成與該屬性相同的靜態(tài)字段
@JvmStatic注解:在單例對(duì)象和伴生對(duì)象中生成對(duì)應(yīng)的靜態(tài)方法

10 sealed 密封類

10.1 為什么使用密封類

就像我們?yōu)槭裁匆胑num類型一樣,比如你有一個(gè)enum類型 MoneyUnit,定義了元、角、分這些單位。枚舉就是為了控制住你所有要的情況是正確的,而不是用硬編碼方式寫(xiě)成字符串“元”,“角”,“分”。

同樣,sealed的目的類似,一個(gè)類之所以設(shè)計(jì)成sealed,就是為了限制類的繼承結(jié)構(gòu),將一個(gè)值限制在有限集中的類型中,而不能有任何其他的類型。

在某種意義上,sealed類是枚舉類的擴(kuò)展:枚舉類型的值集合也是受限的,但每個(gè)枚舉常量只存在一個(gè)實(shí)例,而密封類的一個(gè)子類可以有可包含狀態(tài)的多個(gè)實(shí)例。

10.1 聲明密封類

要聲明一個(gè)密封類,需要在類名前面添加 sealed 修飾符。密封類的所有子類都必須與密封類在同一個(gè)文件中聲明(在 Kotlin 1.1 之前, 該規(guī)則更加嚴(yán)格:子類必須嵌套在密封類聲明的內(nèi)部):


sealed class Expression

class Unit : Expression()
data class Const(val number: Double) : Expression()
data class Sum(val e1: Expression, val e2: Expression) : Expression()
data class Multiply(val e1: Expression, val e2: Expression) : Expression()
object NaN : Expression()


使用密封類的主要場(chǎng)景是在使用 when 表達(dá)式的時(shí)候,能夠驗(yàn)證語(yǔ)句覆蓋了所有情況,而無(wú)需再添加一個(gè) else 子句:

fun eval(expr: Expression): Double = when (expr) {
    is Unit -> 1.0
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    is Multiply -> eval(expr.e1) * eval(expr.e2)
    NaN -> Double.NaN
// 不再需要 `else` 子句,因?yàn)槲覀円呀?jīng)覆蓋了所有的情況
}

測(cè)試代碼:

fun main(args: Array<String>) {
    val u = eval(Unit())
    val a = eval(Const(1.1))
    val b = eval(Sum(Const(1.0), Const(9.0)))
    val c = eval(Multiply(Const(10.0), Const(10.0)))
    println(u)
    println(a)
    println(b)
    println(c)
}


輸出:

1.0
1.1
10.0
100.0

11 data 數(shù)據(jù)類

11.1 構(gòu)造函數(shù)中的 val/var

在開(kāi)始講數(shù)據(jù)類之前,我們先來(lái)看一下幾種類聲明的寫(xiě)法。

寫(xiě)法一:

class Aook(name: String)

這樣寫(xiě),這個(gè)name變量是無(wú)法被外部訪問(wèn)到的。它對(duì)應(yīng)的反編譯之后的Java代碼如下:

public final class Aook {
   public Aook(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
   }
}

寫(xiě)法二:
要想這個(gè)name變量被訪問(wèn)到,我們可以在類體中再聲明一個(gè)變量,然后把這個(gè)構(gòu)造函數(shù)中的參數(shù)賦值給它:

class Cook(name: String) {
    val name = name
}

測(cè)試代碼:

    val cook = Cook("Cook")
    cook.name

對(duì)應(yīng)的Java實(shí)現(xiàn)代碼是:

public final class Cook {
   @NotNull
   private final String name;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public Cook(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.name = name;
   }
}

寫(xiě)法三:

class Dook(val name: String)
class Eook(var name: String)

構(gòu)造函數(shù)中帶var、val修飾的變量,Kotlin編譯器會(huì)自動(dòng)為它們生成getter、setter函數(shù)。

上面的寫(xiě)法對(duì)應(yīng)的Java代碼就是:

public final class Dook {
   @NotNull
   private final String name;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public Dook(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.name = name;
   }
}

public final class Eook {
   @NotNull
   private String name;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }

   public Eook(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.name = name;
   }
}

測(cè)試代碼:

    val dook = Dook("Dook")
    dook.name
    val eook = Eook("Eook")
    eook.name

下面我們來(lái)學(xué)習(xí)一下Kotlin中的數(shù)據(jù)類: data class

11.2 領(lǐng)域?qū)嶓w類

我們寫(xiě)Java代碼的時(shí)候,會(huì)經(jīng)常創(chuàng)建一些只保存數(shù)據(jù)的類。比如說(shuō):

  • POJO類:POJO全稱是Plain Ordinary Java Object / Pure Old Java Object,中文可以翻譯成:普通Java類,具有一部分getter/setter方法的那種類就可以稱作POJO。

  • DTO類:Data Transfer Object,數(shù)據(jù)傳輸對(duì)象類,泛指用于展示層與服務(wù)層之間的數(shù)據(jù)傳輸對(duì)象。

  • VO類:VO有兩種說(shuō)法,一個(gè)是ViewObject,一個(gè)是ValueObject。

  • PO類:Persisent Object,持久對(duì)象。它們是由一組屬性和屬性的get和set方法組成。PO是在持久層所使用,用來(lái)封裝原始數(shù)據(jù)。

  • BO類:Business Object,業(yè)務(wù)對(duì)象層,表示應(yīng)用程序領(lǐng)域內(nèi)“事物”的所有實(shí)體類。

  • DO類:Domain Object,領(lǐng)域?qū)ο螅褪菑默F(xiàn)實(shí)世界中抽象出來(lái)的有形或無(wú)形的業(yè)務(wù)實(shí)體。

等等。

這些我們統(tǒng)稱為領(lǐng)域模型中的實(shí)體類。最簡(jiǎn)單的實(shí)體類是POJO類,含有屬性及屬性對(duì)應(yīng)的set和get方法,實(shí)體類常見(jiàn)的方法還有用于輸出自身數(shù)據(jù)的toString方法。

11.3 數(shù)據(jù)類data class的概念

在 Kotlin 中,也有對(duì)應(yīng)這樣的領(lǐng)域?qū)嶓w類的概念,并在語(yǔ)言層面上做了支持,叫做數(shù)據(jù)類 :

data class Book(val name: String)
data class Fook(var name: String)
data class User(
        val name: String,
        val gender: String,
        val age: Int
) {
    fun validate(): Boolean {
        return true
    }
}


這里的var/val是必須要帶上的。因?yàn)榫幾g器要把主構(gòu)造函數(shù)中聲明的所有屬性,自動(dòng)生成以下函數(shù):

equals()/hashCode() 
toString() : 格式是 User(name=Jacky, gender=Male, age=10)
componentN() 函數(shù) : 按聲明順序?qū)?yīng)于所有屬性component1()、component2() ...
copy() 函數(shù)

如果我們自定義了這些函數(shù),或者繼承父類重寫(xiě)了這些函數(shù),編譯器就不會(huì)再去生成。

測(cè)試代碼:

    val book = Book("Book")
    book.name
    book.copy("Book2")

    val jack = User("Jack", "Male", 1)
    jack.name
    jack.gender
    jack.age
    jack.toString()
    jack.validate()


    val olderJack = jack.copy(age = 2)
    val anotherJack = jack.copy(name = "Jacky", age = 10)

在一些場(chǎng)景下,我們需要復(fù)制一個(gè)對(duì)象來(lái)改變它的部分屬性,而其余部分保持不變。 copy() 函數(shù)就是為此而生成。例如上面的的 User 類的copy函數(shù)的使用:

    val olderJack = jack.copy(age = 2)
    val anotherJack = jack.copy(name = "Jacky", age = 10)

11.4 數(shù)據(jù)類的限制

數(shù)據(jù)類有以下的限制要求:

1.主構(gòu)造函數(shù)需要至少有一個(gè)參數(shù)。下面的寫(xiě)法是錯(cuò)誤的:

data class Gook // error, data class must have at least one primary constructor parameter

2.主構(gòu)造函數(shù)的所有參數(shù)需要標(biāo)記為 val 或 var;

data class Hook(name: String)// error, data class must have only var/val property

跟普通類一樣,數(shù)據(jù)類也可以有次級(jí)構(gòu)造函數(shù):

data class LoginUser(val name: String = "", val password: String = "") : DBase(), IBaseA, IBaseB {

    var isActive = true

    constructor(name: String, password: String, isActive: Boolean) : this(name, password) {
        this.isActive = isActive
    }
    ...
}

3.數(shù)據(jù)類不能是抽象、開(kāi)放、密封或者內(nèi)部的。也就是說(shuō),下面的寫(xiě)法都是錯(cuò)誤的:

abstract data class Iook(val name: String) // modifier abstract is incompatible with data
open data class Jook(val name: String) // modifier abstract is incompatible with data
sealed data class Kook(val name: String)// modifier sealed is incompatible with data
inner data class Look(val name: String)// modifier inner is incompatible with data

數(shù)據(jù)類只能是final的:

final data class Mook(val name: String) // modifier abstract is incompatible with data

4.在1.1之前數(shù)據(jù)類只能實(shí)現(xiàn)接口。自 1.1 起,數(shù)據(jù)類可以擴(kuò)展其他類。代碼示例:


open class DBase
interface IBaseA
interface IBaseB

data class LoginUser(val name: String, val password: String) : DBase(), IBaseA, IBaseB {

    override fun equals(other: Any?): Boolean {
        return super.equals(other)
    }

    override fun hashCode(): Int {
        return super.hashCode()
    }

    override fun toString(): String {
        return super.toString()
    }

    fun validate(): Boolean {
        return true
    }
}

測(cè)試代碼:

    val loginUser1 = LoginUser("Admin", "admin")
    println(loginUser1.component1())
    println(loginUser1.component2())
    println(loginUser1.name)
    println(loginUser1.password)
    println(loginUser1.toString())

輸出:

Admin
admin
Admin
admin
com.easy.kotlin.LoginUser@7440e464

可以看出,由于我們重寫(xiě)了override fun toString(): String, 對(duì)應(yīng)的輸出使我們熟悉的類的輸出格式。

如果我們不重寫(xiě)這個(gè)toString函數(shù),則會(huì)默認(rèn)輸出:

LoginUser(name=Admin, password=admin)

上面的類聲明的構(gòu)造函數(shù),要求我們每次必須初始化name、password的值,如果我們想擁有一個(gè)無(wú)參的構(gòu)造函數(shù),我們只要對(duì)所有的屬性指定默認(rèn)值即可:

data class LoginUser(val name: String = "", val password: String = "") : DBase(), IBaseA, IBaseB {
...
}

這樣我們?cè)趧?chuàng)建對(duì)象的時(shí)候,就可以直接使用:

    val loginUser3 = LoginUser()
    loginUser3.name
    loginUser3.password

11.5 數(shù)據(jù)類的解構(gòu)

解構(gòu)相當(dāng)于 Component 函數(shù)的逆向映射:

    val helen = User("Helen", "Female", 15)
    val (name, gender, age) = helen
    println("$name, $gender, $age years of age")

輸出:

Helen, Female, 15 years of age

11.6 標(biāo)準(zhǔn)數(shù)據(jù)類PairTriple

標(biāo)準(zhǔn)庫(kù)中的二元組 Pair類就是一個(gè)數(shù)據(jù)類:

public data class Pair<out A, out B>(
        public val first: A,
        public val second: B) : Serializable {
    public override fun toString(): String = "($first, $second)"
}

Kotlin標(biāo)準(zhǔn)庫(kù)中,對(duì)Pair類還增加了轉(zhuǎn)換成List的擴(kuò)展函數(shù):

public fun <T> Pair<T, T>.toList(): List<T> = listOf(first, second)

還有三元組Triple類:

public data class Triple<out A, out B, out C>(
        public val first: A,
        public val second: B,
        public val third: C) : Serializable {
    public override fun toString(): String = "($first, $second, $third)"
}
 fun <T> Triple<T, T, T>.toList(): List<T> = listOf(first, second, third)

12 嵌套類(Nested Class)

12.1 嵌套類:類中的類

類可以嵌套在其他類中,可以嵌套多層:

class NestedClassesDemo {
    class Outer {
        private val zero: Int = 0
        val one: Int = 1

        class Nested {
            fun getTwo() = 2
            class Nested1 {
                val three = 3
                fun getFour() = 4
            }
        }
    }
}

測(cè)試代碼:

    val one = NestedClassesDemo.Outer().one
    val two = NestedClassesDemo.Outer.Nested().getTwo()
    val three = NestedClassesDemo.Outer.Nested.Nested1().three
    val four = NestedClassesDemo.Outer.Nested.Nested1().getFour()
    println(one)
    println(two)
    println(three)
    println(four)

我們可以看出,訪問(wèn)嵌套類的方式是直接使用 類名., 有多少層嵌套,就用多少層類名來(lái)訪問(wèn)。

普通的嵌套類,沒(méi)有持有外部類的引用,所以是無(wú)法訪問(wèn)外部類的變量的:

class NestedClassesDemo {
class Outer {
        private val zero: Int = 0
        val one: Int = 1


        class Nested {
            fun getTwo() = 2

            fun accessOuter() = {
                println(zero) // error, cannot access outer class
                println(one)  // error, cannot access outer class
            }
        }
}
}

我們?cè)贜ested類中,訪問(wèn)不到Outer類中的變量zero,one。
如果想要訪問(wèn)到,我們只需要在Nested類前面加上inner關(guān)鍵字修飾,表明這是一個(gè)嵌套的內(nèi)部類。

12.2 內(nèi)部類(Inner Class)

類可以標(biāo)記為 inner 以便能夠訪問(wèn)外部類的成員。內(nèi)部類會(huì)帶有一個(gè)對(duì)外部類的對(duì)象的引用:

class NestedClassesDemo {
class Outer {
        private val zero: Int = 0
        val one: Int = 1

        inner class Inner {
            fun accessOuter() = {
                println(zero) // works
                println(one) // works
            }

        }
}

測(cè)試代碼:

val innerClass = NestedClassesDemo.Outer().Inner().accessOuter()

我們可以看到,當(dāng)訪問(wèn)inner class Inner的時(shí)候,我們使用的是Outer().Inner(), 這是持有了Outer的對(duì)象引用。跟普通嵌套類直接使用類名訪問(wèn)的方式區(qū)分。

12.3 匿名內(nèi)部類(Annonymous Inner Class)

匿名內(nèi)部類,就是沒(méi)有名字的內(nèi)部類。既然是內(nèi)部類,那么它自然也是可以訪問(wèn)外部類的變量的。

我們使用對(duì)象表達(dá)式創(chuàng)建一個(gè)匿名內(nèi)部類實(shí)例:

class NestedClassesDemo {
class AnonymousInnerClassDemo {
            var isRunning = false
            fun doRun() {
                Thread(object : Runnable {
                    override fun run() {
                        isRunning = true
                        println("doRun : i am running, isRunning = $isRunning")
                    }
                }).start()
            }
}
}

如果對(duì)象是函數(shù)式 Java 接口,即具有單個(gè)抽象方法的 Java 接口的實(shí)例,例如上面的例子中的Runnable接口:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

我們可以使用lambda表達(dá)式創(chuàng)建它,下面的幾種寫(xiě)法都是可以的:

            fun doStop() {
                var isRunning = true
                Thread({
                    isRunning = false
                    println("doStop: i am not running, isRunning = $isRunning")
                }).start()
            }

            fun doWait() {
                var isRunning = true

                val wait = Runnable {
                    isRunning = false
                    println("doWait: i am waiting, isRunning = $isRunning")
                }

                Thread(wait).start()
            }

            fun doNotify() {
                var isRunning = true

                val wait = {
                    isRunning = false
                    println("doNotify: i notify, isRunning = $isRunning")
                }

                Thread(wait).start()
            }

測(cè)試代碼:

    NestedClassesDemo.Outer.AnonymousInnerClassDemo().doRun()
    NestedClassesDemo.Outer.AnonymousInnerClassDemo().doStop()
    NestedClassesDemo.Outer.AnonymousInnerClassDemo().doWait()
    NestedClassesDemo.Outer.AnonymousInnerClassDemo().doNotify()

輸出:

doRun : i am running, isRunning = true
doStop: i am not running, isRunning = false
doWait: i am waiting, isRunning = false
doNotify: i notify, isRunning = false

關(guān)于lambda表達(dá)式以及函數(shù)式編程,我們將在下一章中學(xué)習(xí)。

13 委托(Delegation)

13.1 代理模式(Proxy Pattern)

代理模式,也稱委托模式。

在代理模式中,有兩個(gè)對(duì)象參與處理同一個(gè)請(qǐng)求,接受請(qǐng)求的對(duì)象將請(qǐng)求委托給另一個(gè)對(duì)象來(lái)處理。代理模式是一項(xiàng)基本技巧,許多其他的模式,如狀態(tài)模式、策略模式、訪問(wèn)者模式本質(zhì)上是在特殊的場(chǎng)合采用了代理模式。

代理模式使得我們可以用聚合來(lái)替代繼承,它還使我們可以模擬mixin(混合類型)。委托模式的作用是將委托者與實(shí)際實(shí)現(xiàn)代碼分離出來(lái),以達(dá)成解耦的目的。

一個(gè)代理模式的Java代碼示例:

package com.easy.kotlin;

/**
 * Created by jack on 2017/7/5.
 */
interface JSubject {
    public void request();
}

class JRealSubject implements JSubject {
    @Override
    public void request() {
        System.out.println("JRealSubject Requesting");
    }
}

class JProxy implements JSubject {
    private JSubject subject = null;

    //通過(guò)構(gòu)造函數(shù)傳遞代理者
    public JProxy(JSubject sub) {
        this.subject = sub;
    }

    @Override
    public void request() { //實(shí)現(xiàn)接口中定義的方法
        this.before();
        this.subject.request();
        this.after();
    }

    private void before() {
        System.out.println("JProxy Before Requesting ");
    }

    private void after() {
        System.out.println("JProxy After Requesting ");
    }
}

public class DelegateDemo {
    public static void main(String[] args) {
        JRealSubject jRealSubject = new JRealSubject();
        JProxy jProxy = new JProxy(jRealSubject);
        jProxy.request();
    }
}

輸出:

JProxy Before Requesting
JRealSubject Requesting
JProxy After Requesting

13.2 類的委托(Class Delegation)

就像支持單例模式的object對(duì)象一樣,Kotlin 在語(yǔ)言層面原生支持委托模式。

代碼示例:

package com.easy.kotlin

import java.util.*

/**
 * Created by jack on 2017/7/5.
 */

interface Subject {
    fun hello()
}

class RealSubject(val name: String) : Subject {
    override fun hello() {
        val now = Date()
        println("Hello, REAL $name! Now is $now")
    }
}

class ProxySubject(val sb: Subject) : Subject by sb {
    override fun hello() {
        println("Before ! Now is ${Date()}")
        sb.hello()
        println("After ! Now is ${Date()}")
    }
}

fun main(args: Array<String>) {
    val subject = RealSubject("World")
    subject.hello()
    println("-------------------------")
    val proxySubject = ProxySubject(subject)
    proxySubject.hello()
}

在這個(gè)例子中,委托代理類 ProxySubject 繼承接口 Subject,并將其所有共有的方法委托給一個(gè)指定的對(duì)象sb :

class ProxySubject(val sb: Subject) : Subject by sb 

ProxySubject 的超類型Subject中的 by sb 表示sb 將會(huì)在 ProxySubject 中內(nèi)部存儲(chǔ)。

另外,我們?cè)诟采w重寫(xiě)了函數(shù)override fun hello()

測(cè)試代碼:

fun main(args: Array<String>) {
    val subject = RealSubject("World")
    subject.hello()
    println("-------------------------")
    val proxySubject = ProxySubject(subject)
    proxySubject.hello()
}

輸出:

Hello, REAL World! Now is Wed Jul 05 02:45:42 CST 2017
-------------------------
Before ! Now is Wed Jul 05 02:45:42 CST 2017
Hello, REAL World! Now is Wed Jul 05 02:45:42 CST 2017
After ! Now is Wed Jul 05 02:45:42 CST 2017

13.3 委托屬性 (Delegated Properties)

通常對(duì)于屬性類型,我們是在每次需要的時(shí)候手動(dòng)聲明它們:

class NormalPropertiesDemo {
    var content: String = "NormalProperties init content"
}

那么這個(gè)content屬性將會(huì)很“呆板”。屬性委托賦予了屬性富有變化的活力。

例如:

  • 延遲屬性(lazy properties): 其值只在首次訪問(wèn)時(shí)計(jì)算
  • 可觀察屬性(observable properties): 監(jiān)聽(tīng)器會(huì)收到有關(guān)此屬性變更的通知
  • 把多個(gè)屬性儲(chǔ)存在一個(gè)映射(map)中,而不是每個(gè)存在單獨(dú)的字段中。

委托屬性

Kotlin 支持 委托屬性:

class DelegatePropertiesDemo {
    var content: String by Content()

    override fun toString(): String {
        return "DelegatePropertiesDemo Class"
    }
}

class Content {
    operator fun getValue(delegatePropertiesDemo: DelegatePropertiesDemo, property: KProperty<*>): String {
        return "${delegatePropertiesDemo} property '${property.name}' = 'Balalala ... ' "
    }

    operator fun setValue(delegatePropertiesDemo: DelegatePropertiesDemo, property: KProperty<*>, value: String) {
        println("${delegatePropertiesDemo} property '${property.name}' is setting value: '$value'")
    }
}

var content: String by Content()中, by 后面的表達(dá)式的Content()就是該屬性 委托的對(duì)象。content屬性對(duì)應(yīng)的 get()(和 set())會(huì)被委托給Content()operator fun getValue()operator fun setValue() 函數(shù),這兩個(gè)函數(shù)是必須的,而且得是操作符函數(shù)。

測(cè)試代碼:

    val n = NormalPropertiesDemo()
    println(n.content)
    n.content = "Lao tze"
    println(n.content)

    val e = DelegatePropertiesDemo()
    println(e.content) // call Content.getValue
    e.content = "Confucius" // call Content.setValue
    println(e.content) // call Content.getValue

輸出:

NormalProperties init content
Lao tze
DelegatePropertiesDemo Class property 'content' = 'Balalala ... ' 
DelegatePropertiesDemo Class property 'content' is setting value: 'Confucius'
DelegatePropertiesDemo Class property 'content' = 'Balalala ... 

懶加載屬性委托 lazy

lazy() 函數(shù)定義如下:

@kotlin.jvm.JvmVersion
public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

它接受一個(gè) lambda 并返回一個(gè) Lazy <T> 實(shí)例的函數(shù),返回的實(shí)例可以作為實(shí)現(xiàn)懶加載屬性的委托:

第一次調(diào)用 get() 會(huì)執(zhí)行已傳遞給 lazy() 的 lamda 表達(dá)式并記錄下結(jié)果, 后續(xù)調(diào)用 get() 只是返回之前記錄的結(jié)果。

代碼示例:

    val synchronizedLazyImpl = lazy({
        println("lazyValueSynchronized1  3!")
        println("lazyValueSynchronized1  2!")
        println("lazyValueSynchronized1  1!")
        "Hello, lazyValueSynchronized1 ! "
    })

    val lazyValueSynchronized1: String by synchronizedLazyImpl
    println(lazyValueSynchronized1)
    println(lazyValueSynchronized1)

    val lazyValueSynchronized2: String by lazy {
        println("lazyValueSynchronized2  3!")
        println("lazyValueSynchronized2  2!")
        println("lazyValueSynchronized2  1!")
        "Hello, lazyValueSynchronized2 ! "
    }

    println(lazyValueSynchronized2)
    println(lazyValueSynchronized2)


輸出:

lazyValueSynchronized1  3!
lazyValueSynchronized1  2!
lazyValueSynchronized1  1!
Hello, lazyValueSynchronized1 ! 
Hello, lazyValueSynchronized1 ! 


lazyValueSynchronized2  3!
lazyValueSynchronized2  2!
lazyValueSynchronized2  1!
Hello, lazyValueSynchronized2 ! 
Hello, lazyValueSynchronized2 ! 

默認(rèn)情況下,對(duì)于 lazy 屬性的求值是同步的(synchronized), 下面兩種寫(xiě)法是等價(jià)的:

    val synchronizedLazyImpl = lazy({
        println("lazyValueSynchronized1  3!")
        println("lazyValueSynchronized1  2!")
        println("lazyValueSynchronized1  1!")
        "Hello, lazyValueSynchronized1 ! "
    })
    
    val synchronizedLazyImpl2 = lazy(LazyThreadSafetyMode.SYNCHRONIZED, {
        println("lazyValueSynchronized1  3!")
        println("lazyValueSynchronized1  2!")
        println("lazyValueSynchronized1  1!")
        "Hello, lazyValueSynchronized1 ! "
    })

該值是線程安全的。所有線程會(huì)看到相同的值。

如果初始化委托多個(gè)線程可以同時(shí)執(zhí)行,不需要同步鎖,使用LazyThreadSafetyMode.PUBLICATION

    val lazyValuePublication: String by lazy(LazyThreadSafetyMode.PUBLICATION, {
        println("lazyValuePublication 3!")
        println("lazyValuePublication 2!")
        println("lazyValuePublication 1!")
        "Hello, lazyValuePublication ! "
    })

而如果屬性的初始化是單線程的,那么我們使用 LazyThreadSafetyMode.NONE 模式(性能最高):

    val lazyValueNone: String by lazy(LazyThreadSafetyMode.NONE, {
        println("lazyValueNone 3!")
        println("lazyValueNone 2!")
        println("lazyValueNone 1!")
        "Hello, lazyValueNone ! "
    })

Delegates.observable 可觀察屬性委托

我們把屬性委托給Delegates.observable函數(shù),當(dāng)屬性值被重新賦值的時(shí)候, 觸發(fā)其中的回調(diào)函數(shù) onChange。

該函數(shù)定義如下:

public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
        ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
            override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
        }

代碼示例:

class PostHierarchy {
    var level: String by Delegates.observable("P0",
            { property: KProperty<*>,
              oldValue: String,
              newValue: String ->
                println("$oldValue -> $newValue")
            })
}


測(cè)試代碼:

    val ph = PostHierarchy()
    ph.level = "P1"
    ph.level = "P2"
    ph.level = "P3"
    println(ph.level) // P3

輸出:

P0 -> P1
P1 -> P2
P2 -> P3
P3

我們可以看出,屬性level每次賦值,都回調(diào)了Delegates.observable中的lambda表達(dá)式所寫(xiě)的onChange函數(shù)。

Delegates.vetoable 可否決屬性委托

這個(gè)函數(shù)定義如下:

public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean):
        ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
            override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
        }

當(dāng)我們把屬性委托給這個(gè)函數(shù)時(shí),我們可以通過(guò)onChange函數(shù)的返回值是否為true, 來(lái)選擇屬性的值是否需要改變。

代碼示例:

class PostHierarchy {
    var grade: String by Delegates.vetoable("T0", {
        property, oldValue, newValue ->
        true
    })

    var notChangeGrade: String by Delegates.vetoable("T0", {
        property, oldValue, newValue ->
        false
    })
}

測(cè)試代碼:

    ph.grade = "T1"
    ph.grade = "T2"
    ph.grade = "T3"
    println(ph.grade) // T3

    ph.notChangeGrade = "T1"
    ph.notChangeGrade = "T2"
    ph.notChangeGrade = "T3"
    println(ph.notChangeGrade) // T0

我們可以看出,當(dāng)onChange函數(shù)返回值是false的時(shí)候,對(duì)屬性notChangeGrade的賦值都沒(méi)有生效,依然是原來(lái)的默認(rèn)值T0 。

Delegates.notNull 非空屬性委托

我們也可以使用委托來(lái)實(shí)現(xiàn)屬性的非空限制:

var name: String by Delegates.notNull()

這樣name屬性就被限制為不能為null,如果被賦值null,編譯器直接報(bào)錯(cuò):

ph.name = null // error 
Null can not be a value of a non-null type String

屬性委托給Map映射

我們也可以把屬性委托給Map:

class Account(val map: Map<String, Any?>) {
    val name: String by map
    val password: String by map
}

測(cè)試代碼:

val account = Account(mapOf(
            "name" to "admin",
            "password" to "admin"
    ))

println("Account(name=${account.name}, password = ${account.password})")

輸出:

Account(name=admin, password = admin)

如果是可變屬性,這里也可以把只讀的 Map 換成 MutableMap :

class MutableAccount(val map: MutableMap<String, Any?>) {
    var name: String by map
    var password: String by map
}

測(cè)試代碼:

val maccount = MutableAccount(mutableMapOf(
            "name" to "admin",
            "password" to "admin"
))

maccount.password = "root"
println("MutableAccount(name=${maccount.name}, password = ${maccount.password})")

輸出:

MutableAccount(name=admin, password = root)

這節(jié)介紹了Kotlin面向?qū)ο缶幊痰奶匦裕?類與構(gòu)造函數(shù)、抽象類與接口、繼承以及多重繼承等基礎(chǔ)知識(shí),同時(shí)介紹了Kotlin中的注解類、枚舉類、數(shù)據(jù)類、密封類、嵌套類、內(nèi)部類、匿名內(nèi)部類等特性類。最后我們學(xué)習(xí)了Kotlin中對(duì)單例模式、委托模式的語(yǔ)言層面上的內(nèi)置支持:object對(duì)象、委托。

總的來(lái)說(shuō),Kotlin相比于Java的面向?qū)ο缶幊蹋黾硬簧儆腥さ墓δ芘c特性支持,這使得我們代碼寫(xiě)起來(lái)更加方便快捷了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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