點擊這裏 > 去京東商城購買閱讀
點擊這裏 > 去天貓商城購買閱讀
在前面的章節中,咱們學習了Kotlin的語言基礎知識、類型系統、集合類以及泛型相關的知識。在本章節以及下一章中,咱們將一塊兒來學習Kotlin對面向對象編程以及函數式編程的支持。html
《易傳·繫辭上傳》:「易有太極,是生兩儀,兩儀生四象,四象生八卦。」 現在的互聯網世界,其基石倒是01(陰陽),不得不佩服我華夏先祖的博大精深的智慧。java
一切皆是映射git
計算機領域中的全部問題,均可以經過向上一層進行抽象封裝來解決.這裏的封裝的本質概念,其實就是「映射」。github
就比如經過的電子電路中的電平進行01邏輯映射,因而有了布爾代數,數字邏輯電路系統;算法
對01邏輯的進一步封裝抽象成CPU指令集映射,誕生了彙編語言;express
經過彙編語言的向上抽象一層編譯解釋器,因而有了pascal,fortran,C語言;
再對核心函數api進行封裝造成開發包(Development Kit), 因而有了Java,C++ 。編程
從面向過程到面向對象,再到設計模式,架構設計,面向服務,Sass/Pass/Iass等等的思想,各類軟件理論思想五花八門,但萬變不離其宗——設計模式
Grady Booch:我對OO編程的目標歷來就不是複用。相反,對我來講,對象提供了一種處理複雜性的方式。這個問題能夠追溯到亞里士多德:您把這個世界視爲過程仍是對象?在OO興起運動以前,編程以過程爲中心--例如結構化設計方法。然而,系統已經到達了超越其處理能力的複雜性極點。有了對象,咱們可以經過提高抽象級別來構建更大的、更復雜的系統--我認爲,這纔是面向對象編程運動的真正勝利。api
最初, 人們使用物理的或邏輯的二進制機器指令來編寫程序, 嘗試着表達思想中的邏輯, 控制硬件計算和顯示, 發現是可行的;安全
接着, 創造了助記符 —— 彙編語言, 比機器指令更容易記憶;
再接着, 創造了編譯器、解釋器和計算機高級語言, 可以以人類友好天然的方式去編寫程序, 在犧牲少許性能的狀況下, 得到比彙編語言更強且更容易使用的語句控制能力:條件、分支、循環, 以及更多的語言特性: 指針、結構體、聯合體、枚舉等, 還創造了函數, 可以將一系列指令封裝成一個獨立的邏輯塊反覆使用;
逐漸地,產生了面向過程的編程方法;
後來, 人們發現將數據和邏輯封裝成對象, 更接近於現實世界, 且更容易維護大型軟件, 又出現了面向對象的編程語言和編程方法學, 增長了新的語言特性: 繼承、 多態、 模板、 異常錯誤。
爲了避免必重複開發常見工具和任務, 人們創造和封裝了容器及算法、SDK, 垃圾回收器, 甚至是併發庫;
爲了讓計算機語言更有力更有效率地表達各類現實邏輯, 消解軟件開發中遇到的衝突, 還在語言中支持了元編程、 高階函數, 閉包 等有用特性。
爲了更高效率地開發可靠的軟件和應用程序, 人們逐漸構建了代碼編輯器、 IDE、 代碼版本管理工具、公共庫、應用框架、 可複用組件、系統規範、網絡協議、 語言標準等, 針對遇到的問題提出了許多不一樣的思路和解決方案, 並總結提煉成特定的技術和設計模式, 還探討和造成了很多軟件開發過程, 用來保證最終發佈的軟件質量。 儘管編寫的這些軟件和工具還存在很多 BUG ,可是它們都「奇蹟般地存活」, 並共同構建了今天蔚爲壯觀的互聯網時代的電商,互聯網金融,雲計算,大數據,物聯網,機器智能等等的「虛擬世界」。
二進制數是用0和1兩個數碼來表示的數。它的基數爲2,進位規則是「逢二進一」,借位規則是「借一當二」,由18世紀德國數理哲學大師萊布尼茲發現。當前的計算機系統使用的基本上是二進制系統。
19世紀愛爾蘭邏輯學家B對邏輯命題的思考過程轉化爲對符號0,1的某種代數演算,二進制是逢2進位的進位制。0、1是基本算符。由於它只使用0、1兩個數字符號,很是簡單方便,易於用電子方式實現。
二進制的發現直接致使了電子計算器和計算機的發明,並讓計算機獲得了迅速的普及,進入各行各業,成爲人類生活和生產的重要工具。
二進制的實質是經過兩個數字「0」和「1」來描述事件。在人類的生產、生活等許多領域,咱們能夠經過計算機來虛擬地描述現實中存在的事件,並能經過給定的條件和參數模擬事件變化的規律。二進制的計算機幾乎是萬能的,能將咱們生活的現實世界完美複製,而且還能根據咱們人類給定的條件模擬在現實世界難以實現的各類實驗。
可是,不論計算機能給咱們如何多變、如何完美、如何複雜的畫面,其本源只是簡單的「0」和「1」。「0」和「1」在計算機中經過不一樣的組合與再組合,模擬出一個紛繁複雜、一應俱全的虛擬世界。咱們簡單圖示以下:
二進制的「0」和「1」經過計算機裏可以創造出一個虛擬的、紛繁的世界。天然界中的陰陽造成了現實世界的萬事萬物。
因此天然世界的「陰」「陽」做爲基礎切實地造就了複雜的現實世界,計算機的「0」和「1」形象地模擬現實世界的一切現象,易學中的「卦」和「陰陽爻」抽象地揭示了天然界存在的事件和其變化規律。
因此說,編程的本質跟大天然創造萬物的本質是同樣的。
從IBM公司的約翰·巴庫斯在1957年開發出世界上第一個高級程序設計語言Fortran至今,高級程序設計語言的發展已經經歷了整整半個世紀。在這期間,程序設計語言主要經歷了從面向過程(如C和Pascal語言)到面向對象(如C++和Java語言),再到面向組件編程(如.NET平臺下的C#語言),以及面向服務架構技術(如SOA、Service以及最近很火的微服務架構)等。
結構化編程思想的核心:功能分解(自頂向下,逐層細化)。
1971年4月份的 Communications of ACM上,尼古拉斯·沃斯(Niklaus Wirth,1934年2月15日—, 結構化編程思想的創始人。因發明了Euler、Alogo-W、Modula和Pascal等一系列優秀的編程語言並提出告終構化編程思想而在1984年得到了圖靈獎。)發表了論文「經過逐步求精方式開發程序’(Program Development by Stepwise Refinement),首次提出「結構化程序設計」(structure programming)的概念。
不要求一步就編製成可執行的程序,而是分若干步進行,逐步求精。
第一步編出的程序抽象度最高,第二步編出的程序抽象度有所下降…… 最後一步編出的程序即爲可執行的程序。
用這種方法編程,彷佛複雜,實際上優勢不少,可以使程序易讀、易寫、易調試、易維護、易保證其正確性及驗證其正確性。
結構化程序設計方法又稱爲「自頂向下」或「逐步求精」法,在程序設計領域引起了一場革命,成爲程序開發的一個標準方法,尤爲是在後來發展起來的軟件工程中得到普遍應用。有人評價說Wirth的結構化程序設計概念「徹底改變了人們對程序設計的思惟方式」,這是一點也不誇張的。
尼古拉斯· 沃思教授在編程界提出了一個著名的公式:
程序 = 數據結構 + 算法
面向對象編程思想的核心:應對變化,提升複用。
阿倫·凱(Alan Kay):面向對象編程思想的創始人。2003年因在面向對象編程上所作的巨大貢獻而得到圖靈獎。
The best way to predict the future is to invent it,預測將來最好的方法是創造它!(Alan Kay)
阿倫·凱是Smalltalk面向對象編程語言的發明人之一,也是面向對象編程思想的創始人之一,同時,他仍是筆記本電腦最先的構想者和現代Windows GUI的建築師。最先提出PC概念和互聯網的也是阿倫·凱,因此人們都尊稱他爲「預言大師」。他是當今IT界屈指可數的技術天才級人物。
面向對象編程思想主要是複用性和靈活性(彈性)。複用性是面向對象編程的一個主要機制。靈活性主要是應對變化的特性,由於客戶的需求是不斷改變的,怎樣適應客戶需求的變化,這是軟件設計靈活性或者說是彈性的問題。
Java是一種面向對象編程語言,它基於Smalltalk語言,做爲OOP語言,它具備如下五個基本特性:
1.萬物皆對象,每個對象都會存儲數據,而且能夠對自身執行操做。所以,每個對象包含兩部分:成員變量和成員方法。在成員方法中能夠改變成員變量的值。
2.程序是對象的集合,他們經過發送消息來告知彼此所要作的事情,也就是調用相應的成員函數。
3.每個對象都有本身的由其餘對象所構成的存儲,也就是說在建立新對象的時候能夠在成員變量中使用已存在的對象。
4.每一個對象都擁有其類型,每一個對象都是某個類的一個實例,每個類區別於其它類的特性就是能夠向它發送什麼類型的消息,也就是它定義了哪些成員函數。
5.某一個特定類型的全部對象均可以接受一樣的消息。另外一種對對象的描述爲:對象具備狀態(數據,成員變量)、行爲(操做,成員方法)和標識(成員名,內存地址)。
面嚮對象語言實際上是對現實生活中的實物的抽象。
每一個對象可以接受的請求(消息)由對象的接口所定義,而在程序中必須由知足這些請求的代碼,這段代碼稱之爲這個接口的實現。當向某個對象發送消息(請求)時,這個對象便知道該消息的目的(該方法的實現已定義),而後執行相應的代碼。
咱們常常說一些代碼片斷是優雅的或美觀的,實際上意味着它們更容易被人類有限的思惟所處理。
對於程序的複合而言,好的代碼是它的表面積要比體積增加的慢。
代碼塊的「表面積」是是咱們複合代碼塊時所須要的信息(接口API協議定義)。代碼塊的「體積」就是接口內部的實現邏輯(API背後的實現代碼)。
在面向對象編程中,一個理想的對象應該是隻暴露它的抽象接口(純表面, 無體積),其方法則扮演箭頭的角色。若是爲了理解一個對象如何與其餘對象進行復合,當你發現不得不深刻挖掘對象的實現之時,此時你所用的編程範式的本來優點就蕩然無存了。
咱們知道面向對象支持重用,可是重用的單元很小,通常是類;而面向組件則不一樣,它能夠重用多個類甚至一個程序。也就是說面向組件支持更大範圍內的重用,開發效率更高。若是把面向對象比做重用零件,那麼面向組件則是重用部件。
將系統進行功能化,每一個功能提供一種服務。如今很是流行微服務MicroService技術以及SOA(面向服務架構)技術。
面向過程(Procedure)→面向對象(Object)→ 面向組件(Component) →面向服務(Service)
正如解決數學問題一般咱們會談「思想」,諸如反證法、化繁爲簡等,解決計算機問題也有不少很是出色的思想。思想之因此稱爲思想,是由於「思想」有拓展性與引導性,能夠解決一系列問題。
解決問題的複雜程度直接取決於抽象的種類及質量。過將結構、性質不一樣的底層實現進行封裝,向上提供統一的API接口,讓使用者以爲就是在使用一個統一的資源,或者讓使用者以爲本身在使用一個原本底層不直接提供、「虛擬」出來的資源。
計算機中的全部問題 , 均可以經過向上抽象封裝一層來解決。一樣的,任何複雜的問題, 最終總可以迴歸最本質,最簡單。
面向對象編程是一種自頂向下的程序設計方法。萬事萬物都是對象,對象有其行爲(方法),狀態(成員變量,屬性)。OOP是一種編程思想,而不是針對某個語言而言的。固然,語言影響思惟方式,思惟依賴語言的表達,這也是辯證的來看。
所謂「面嚮對象語言」,其實經典的「過程式語言」(好比Pascal,C),也能體現面向對象的思想。所謂「類」和「對象」,就是C語言裏面的抽象數據類型結構體(struct)。
而面向對象的多態是惟一相比struct多付出的代價,也是最重要的特性。這就是SmallTalk、Java這樣的面嚮對象語言所提供的特性。
回到一個古老的話題:程序是什麼?
在面向對象的編程世界裏,下面的這個公式
程序 = 算法 + 數據結構
能夠簡單重構成:
程序 = 基於對象操做的算法 + 以對象爲最小單位的數據結構
封裝老是爲了減小操做粒度,數據結構上的封裝致使了數據的減小,天然減小了問題求解的複雜度;對代碼的封裝使得代碼得以複用,減小了代碼的體積,一樣使問題簡化。這個時候,算法操做的就是一個抽象概念的集合。
在面向對象的程序設計中,咱們便少不了集合類容器。容器就用來存放一類有共同抽象概念的東西。這裏說有共同概念的東西(而沒有說對象),其實,就是咱們上一個章節中講到的泛型。這樣對於一個通用的算法,咱們就能夠最大化的實現複用,做用於的集合。
面向對象的本質就是讓對象有多態性,把不一樣對象以同一特性來歸組,統一處理。至於所謂繼承、虛表、等等概念,只是其實現的細節。
在遵循這些面向對象設計原則基礎上,前輩們總結出一些解決不一樣問題場景的設計模式,以GOF的23中設計模式最爲知名。
咱們用一幅圖簡單歸納一下面向對象編程的知識框架:
講了這麼多思考性的思想層面的東西,咱們下面來開始Kotlin的面向對象編程的學習。Kotlin對面向對象編程是徹底支持的。
Kotlin和Java很類似,也是一種面向對象的語言。下面咱們來一塊兒學習Kotlin的面向對象的特性。若是您熟悉Java或者C++、C#中的類,您能夠很快上手。同時,您也將看到Kotlin與Java中的面向對象編程的一些不一樣的特性。
Kotlin中的類和接口跟Java中對應的概念有些不一樣,好比接口能夠包含屬性聲明;Kotlin的類聲明,默認是final和public的。
另外,嵌套類並非默認在內部的。它們不包含外部類的隱式引用。
在構造函數方面,Kotlin簡短的主構造函數在大多數狀況下均可以知足使用,固然若是有稍微複雜的初始化邏輯,咱們也能夠聲明次級構造函數來完成。
咱們還可使用 data 修飾符來聲明一個數據類,使用 object 關鍵字來表示單例對象、伴生對象等。
Kotlin類的成員能夠包含:
等。
和大部分語言相似,Kotlin使用class做爲類的關鍵字,當咱們聲明一個類時,直接經過class加類名的方式來實現:
class World
這樣咱們就聲明瞭一個World類。
在 Kotlin 中,一個類能夠有一個
主構造函數是類頭的一部分,直接放在類名後面:
open class Student constructor(var name: String, var age: Int) : Any() { ... }
若是主構造函數沒有任何註解或者可見性修飾符,能夠省略這個 constructor 關鍵字。若是構造函數有註解或可見性修飾符,這個 constructor 關鍵字是必需的,而且這些修飾符在它前面:
annotation class MyAutowired class ElementaryStudent public @MyAutowired constructor(name: String, age: Int) : Student(name, age) { ... }
與普通屬性同樣,主構造函數中聲明的屬性能夠是可變的(var)或只讀的(val)。
主構造函數不能包含任何的代碼。初始化的代碼能夠放到以 init 關鍵字做爲前綴的初始化塊(initializer blocks)中:
open class Student constructor(var name: String, var age: Int) : Any() { init { println("Student{name=$name, age=$age} created!") } ... }
主構造的參數能夠在初始化塊中使用,也能夠在類體內聲明的屬性初始化器中使用。
在類體中,咱們也能夠聲明前綴有 constructor的次構造函數,次構造函數不能有聲明 val 或 var :
class MiddleSchoolStudent { constructor(name: String, age: Int) { } }
若是類有一個主構造函數,那麼每一個次構造函數須要委託給主構造函數, 委託到同一個類的另外一個構造函數用 this 關鍵字便可:
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 } ... }
若是一個非抽象類沒有聲明任何(主或次)構造函數,它會有一個生成的不帶參數的主構造函數。構造函數的可見性是 public。
咱們若是但願這個構造函數是私有的,咱們能夠以下聲明:
class DontCreateMe private constructor() { }
這樣咱們在代碼中,就沒法直接使用主構造函數來實例化這個類,下面的寫法是不容許的:
val dontCreateMe = DontCreateMe() // cannot access it
可是,咱們能夠經過次構造函數引用這個私有主構造函數來實例化對象:
咱們再給這個World類加入兩個屬性。咱們可能直接簡單地寫成:
class World1 { val yin: Int val yang: Int }
在Kotlin中,直接這樣寫語法上是會報錯的:
意思很明顯,是說這個類的屬性必需要初始化,或者若是不初始化那就得是抽象的abstract屬性。
咱們把這兩個屬性都給初始化以下:
class World1 { val yin: Int = 0 val yang: Int = 1 }
咱們再來使用測試代碼來看下訪問這兩個屬性的方式:
>>> class World1 { ... val yin: Int = 0 ... val yang: Int = 1 ... } >>> val w1 = World1() >>> w1.yin 0 >>> w1.yang 1
上面的World1類的代碼,在Java中等價的寫法是:
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中的類的字段自動帶有getter方法和setter方法。並且寫起來比Java要簡潔的多。
咱們再來給這個World1類中加上一個函數:
class World2 { val yin: Int = 0 val yang: Int = 1 fun plus(): Int { return yin + yang } } val w2 = World2() println(w2.plus()) // 輸出 1
含有抽象函數的類(這樣的類須要使用abstract修飾符來聲明),稱爲抽象類。
下面是一個抽象類的例子:
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 ... ") } }
在上面的這個抽象類中,不只能夠有抽象函數abstract fun doEat()
abstract fun doWalk()
,同時能夠有具體實現的函數fun doSwim()
, 這個函數默認是final的。也就是說,咱們不能重寫這個doSwim函數:
若是一個函數想要設計成能被重寫,例如fun doSleep()
,咱們給它加上open關鍵字便可。而後,咱們就能夠在子類中重寫這個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 ... ") // } }
抽象函數是一種特殊的函數:它只有聲明,而沒有具體的實現。抽象函數的聲明格式爲:
abstract fun doEat()
關於抽象函數的特徵,咱們簡單總結以下:
抽象屬性就是在var或val前被abstract修飾,抽象屬性的聲明格式爲:
abstract var addr : String abstract val weight : Float
關於抽象屬性,須要注意的是:
綜上所述,抽象類和普通類的區別有:
1.抽象函數必須爲public或者protected(由於若是爲private,則不能被子類繼承,子類便沒法實現該方法),缺省狀況下默認爲public。
也就是說,這三個函數
abstract fun doEat() abstract fun doWalk() fun doSwim() { println("I am Swimming ... ") }
默認的都是public的。
另外抽象類中的具體實現的函數,默認是final的。上面的三個函數,等價的Java的代碼以下:
public abstract void doEat(); public abstract void doWalk(); public final void doSwim() { String var1 = "I am Swimming ... "; System.out.println(var1); }
2.抽象類不能用來建立對象實例。也就是說,下面的寫法編譯器是不容許的:
3.若是一個類繼承於一個抽象類,則子類必須實現父類的抽象方法。實現父類抽象函數,咱們使用override關鍵字來代表是重寫函數:
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 ... ") } }
若是子類沒有實現父類的抽象函數,則必須將子類也定義爲爲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函數沒有實現父類的抽象函數,那麼咱們在子類中把它依然定義爲抽象函數。相應地這個子類,也成爲了抽象子類,須要使用abstract關鍵字來聲明。
若是抽象類中含有抽象屬性,再實現子類中必須將抽象屬性初始化,除非子類也爲抽象類。例如咱們聲明一個Teacher類繼承Person類:
class Teacher(name: String, age: Int) : Person(name, age) { override var addr: String // error, 須要初始化,或者聲明爲abstract override val weight: Float // error, 須要初始化,或者聲明爲abstract ... }
這樣寫,編譯器會直接報錯:
解決方法是,在實現的子類中,咱們將抽象屬性初始化便可:
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 ... ") } }
和Java相似,Kotlin使用interface做爲接口的關鍵詞:
interface ProjectService
Kotlin 的接口與 Java 8 的接口相似。與抽象類相比,他們均可以包含抽象的方法以及方法的實現:
interface ProjectService { val name: String val owner: String fun save(project: Project) fun print() { println("I am project") } }
接口是沒有構造函數的。咱們使用冒號:
語法來實現一個接口,若是有多個用,
逗號隔開:
class ProjectServiceImpl : ProjectService class ProjectMilestoneServiceImpl : ProjectService, MilestoneService
咱們也能夠實現多個接口:
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") } }
當子類繼承了某個類以後,即可以使用父類中的成員變量,可是並非徹底繼承父類的全部成員變量。具體的原則以下:
1.可以繼承父類的public和protected成員變量;不可以繼承父類的private成員變量;
2.對於父類的包訪問權限成員變量,若是子類和父類在同一個包下,則子類可以繼承;不然,子類不可以繼承;
3.對於子類能夠繼承的父類成員變量,若是在子類中出現了同名稱的成員變量,則會發生隱藏現象,即子類的成員變量會屏蔽掉父類的同名成員變量。若是要在子類中訪問父類中同名成員變量,須要使用super關鍵字來進行引用。
在kotlin中, 實現繼承一般遵循以下規則:若是一個類從它的直接父類繼承了同一個函數的多個實現,那麼它必須重寫這個函數而且提供本身的實現(或許只是直接用了繼承來的實現) 爲表示使用父類中提供的方法咱們用 super 表示。
在重寫print()
時,由於咱們實現的ProjectService、MilestoneService都有一個print()
函數,當咱們直接使用super.print()
時,編譯器是沒法知道咱們想要調用的是那個裏面的print函數的,這個咱們叫作覆蓋衝突:
這個時候,咱們可使用下面的語法來調用:
super<ProjectService>.print() super<MilestoneService>.print()
在接口中聲明的屬性,能夠是抽象的,或者是提供訪問器的實現。
在企業應用中,大多數的類型都是無狀態的,如:Controller、ApplicationService、DomainService、Repository等。
由於接口沒有狀態, 因此它的屬性是無狀態的。
interface MilestoneService { val name: String // 抽象的 val owner: String get() = "Jack" // 訪問器 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") } }
接口主要是對動做的抽象,定義了行爲特性的規約。
抽象類是對根源的抽象。當你關注一個事物的本質的時候,用抽象類;當你關注一個操做的時候,用接口。
接口不能保存狀態,能夠有屬性但必須是抽象的。
一個類只能繼承一個抽象類,而一個類卻能夠實現多個接口。
類若是要實現一個接口,它必需要實現接口聲明的全部方法。可是,類能夠不實現抽象類聲明的全部方法,固然,在這種狀況下,類也必須得聲明成是抽象的。
接口中全部的方法隱含的都是抽象的。而抽象類則能夠同時包含抽象和非抽象的方法。
抽象類是對一種事物的抽象,即對類抽象,而接口是對行爲的抽象。抽象類是對整個類總體進行抽象,包括屬性、行爲,可是接口倒是對類局部(行爲)進行抽象。
繼承是 is a
的關係,而 接口實現則是 has a
的關係。若是一個類繼承了某個抽象類,則子類一定是抽象類的種類,而接口實現就不須要有這層類型關係。
設計層面不一樣,抽象類做爲不少子類的父類,它是一種模板式設計。而接口是一種行爲規範,它是一種輻射式設計。也就是說:
在實際使用中,使用抽象類(也就是繼承),是一種強耦合的設計,用來描述A is a B
的關係,即若是說A繼承於B,那麼在代碼中將A當作B去使用應該徹底沒有問題。好比在Android中,各類控件均可以被當作View去處理。
若是在你設計中有兩個類型的關係並非is a
,而是is like a
,那就必須慎重考慮繼承。由於一旦咱們使用了繼承,就要當心處理好子類跟父類的耦合依賴關係。組合優於繼承。
繼承是面向對象編程的一個重要的方式,由於經過繼承,子類就能夠擴展父類的功能。
在Kotlin中,全部的類會默認繼承Any這個父類,但Any並不徹底等同於java中的Object類,由於它只有equals(),hashCode()和toString()這三個方法。
除了抽象類、接口默承認以被繼承(實現)外,咱們也能夠把一個類聲明爲open的,這樣咱們就能夠繼承這個open類。
當咱們想定義一個父類時,須要使用open關鍵字:
open class Base{ }
固然,抽象類是默認open的。
而後在子類中使用冒號:
進行繼承
class SubClass : Base(){ }
若是父類有構造函數,那麼必須在子類的主構造函數中進行繼承,沒有的話則能夠選擇主構造函數或二級構造函數
//父類 open class Base(type:String){ } //子類 class SubClass(type:String) : Base(type){ }
Kotlin中的override
重寫和java中也有所不一樣,由於Kotlin提倡全部的操做都是明確的,所以須要將但願被重寫的函數設爲open:
open fun doSomething() {}
而後經過override標記實現重寫
override fun doSomething() { super.doSomething() }
一樣的,抽象函數以及接口中定義的函數默認都是open的。
override重寫的函數也是open的,若是但願它不被重寫,能夠在前面增長final :
open class SubClass : Base{ constructor(type:String) : super(type){ } final override fun doSomething() { super.doSomething() } }
有些編程語言支持一個類擁有多個父類,例如C++。 咱們將這個特性稱之爲多重繼承(multiple inheritance)。多重繼承會有二義性和鑽石型繼承樹(DOD:Diamond Of Death)的複雜性問題。Kotlin跟Java同樣,沒有采用多繼承,任何一個子類僅容許一個父類存在,而在多繼承的問題場景下,使用實現多個interface 組合的方式來實現多繼承的功能。
代碼示例:
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") } } // 始祖鳥, 能飛也能跑 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() }
上述代碼類之間的關係,咱們用圖示以下:
咱們能夠看出,Archaeopteryx繼承了Animal類,用了父類doEat()函數功能;實現了Runnable接口,擁有了doRun()函數規範;實現了Flyable接口,擁有了doFly()函數規範。
在這裏,咱們經過實現多個接口,組合完成了的多個功能,而不是設計多個層次的複雜的繼承關係。
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 }
咱們能夠看出,這個枚舉類有兩個屬性:
public final val name: String public final val ordinal: Int
分別表示的是枚舉對象的值跟下標位置。
同時,咱們能夠看出枚舉類還實現了Comparable<E>接口。
枚舉類的最基本的用法是實現類型安全的枚舉:
enum class Direction { NORTH, SOUTH, WEST, EAST } >>> val north = Direction.NORTH >>> north.name NORTH >>> north.ordinal 0 >>> north is Direction true
每一個枚舉常量都是一個對象。枚舉常量用逗號分隔。
咱們能夠以下初始化枚舉類的值:
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
咱們使用enumValues()函數來列出枚舉的全部值:
@SinceKotlin("1.1") public inline fun <reified T : Enum<T>> enumValues(): Array<T>
每一個枚舉常量,默認都name
名稱和ordinal
位置的屬性(這個跟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
咱們直接聲明瞭一個簡單枚舉類,咱們使用遍歷函數 enumValues<RGB>()
列出了RGB枚舉類的全部枚舉值。使用it.name
it.ordinal
直接訪問各個枚舉值的名稱和位置。
另外,咱們也能夠自定義枚舉屬性值:
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
訪問屬性名來獲得對應的屬性值。
Kotlin 的註解與 Java 的註解徹底兼容。
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
在上面的代碼中,咱們經過向註解類添加元註解(meta-annotation)的方法來指定其餘屬性:
這幾個註解定義在kotlin/annotation/Annotations.kt
類中。
註解能夠用在類、函數、參數、變量(成員變量、局部變量)、表達式、類型上等。這個由該註解的元註解@Target定義。
@MagicClass class Foo @MagicConstructor constructor() { constructor(index: Int) : this() { this.index = index } @MagicClass var index: Int = 0 @MagicFunction fun magic(@MagicClass name: String) { } }
註解在主構造器上,主構造器必須加上關鍵字 「constructor」
@MagicClass class Foo @MagicConstructor constructor() { ... }
單例模式很經常使用。它是一種經常使用的軟件設計模式。例如,Spring中的Bean默認就是單例。經過單例模式能夠保證系統中一個類只有一個實例。即一個類只有一個對象實例。
咱們用Java實現一個簡單的單例類的代碼以下:
class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
測試代碼:
Singleton singleton1 = Singleton.getInstance();
能夠看出,咱們先在單例類中聲明瞭一個私有靜態的Singleton instance
變量,而後聲明一個私有構造函數private Singleton() {}
, 這個私有構造函數使得外部沒法直接經過new的方式來構建對象:
Singleton singleton2 = new Singleton(); //error, cannot private access
最後提供一個public的獲取當前類的惟一實例的靜態方法getInstance()
。咱們這裏給出的是一個簡單的單例類,是線程不安全的。
Kotlin中沒有 靜態屬性和方法,可是也提供了實現相似於單例的功能,咱們可使用關鍵字 object
聲明一個object對象:
object AdminUser { val username: String = "admin" val password: String = "admin" fun getTimestamp() = SimpleDateFormat("yyyyMMddHHmmss").format(Date()) fun md5Password() = EncoderByMd5(password + getTimestamp()) }
測試代碼:
val adminUser = AdminUser.username val adminPassword = AdminUser.md5Password() println(adminUser) // admin println(adminPassword) // g+0yLfaPVYxUf6TMIdXFXw==,這個值具體運行時會變化
爲了方便在REPL中演示說明,咱們再寫一個示例代碼:
>>> object User { ... val username: String = "admin" ... val password: String = "admin" ... }
object對象只能經過對象名字來訪問:
>>> User.username admin >>> User.password admin
不能像下面這樣使用構造函數:
>>> 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對象的概念,咱們把上面的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背後的一些原理。
這個object對象還能夠放到一個類裏面:
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 } } }
測試代碼:
DataProcessor.FileUtils.userHome // /Users/jack/ DataProcessor.FileUtils.getFileContent("test.data") // 輸出文件的內容
一樣的,咱們只能經過類的名稱來直接訪問object,不能使用對象實例引用。下面的寫法是錯誤的:
val dp = DataProcessor() dp.FileUtils.userHome // error, Nested object FileUtils cannot access object via reference
咱們在Java中一般會寫一些Utils類,這樣的類咱們在Kotlin中就能夠直接使用object對象:
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) } }) } }
測試代碼:
val url = "http://www.baidu.com" val html1 = HttpUtils.getSync(url) // 同步get println("html1=${html1}") HttpUtils.getAsync(url) // 異步get
還有,在代碼行內,有時候咱們須要的僅僅是一個簡單的對象,咱們這個時候就可使用下面的匿名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)) }
測試代碼:
distance(3.0, 4.0)
須要注意的是,匿名對象只能夠用在本地和私有做用域中聲明的類型。代碼示例:
class AnonymousObjectType { // 私有函數,返回的是匿名object類型 private fun privateFoo() = object { val x: String = "x" } // 公有函數,返回的類型是 Any fun publicFoo() = object { val x: String = "x" // 沒法訪問到 } 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 匿名內部類相似,object對象表達式中的代碼能夠訪問來自包含它的做用域的變量(與 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) } }) }
測試代碼:
countCompare() countCompare=1 [1, 4, 3, 7, 11, 9, 10, 20] ... countCompare=17 [1, 3, 4, 7, 9, 10, 11, 20]
Kotlin中還提供了 伴生對象 ,用companion object
關鍵字聲明:
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() } } }
一個類只能有1個伴生對象。也就是是下面的寫法是錯誤的:
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() } }
一個類的伴生對象默認引用名是Companion:
class ClassB { companion object { fun create(): ClassB = ClassB() fun get() = "Hi, I am CompanyB" } }
咱們能夠直接像在Java靜態類中使用靜態方法同樣使用一個類的伴生對象的函數,屬性(可是在運行時,它們依舊是實體的實例成員):
ClassB.Companion.index ClassB.Companion.create() ClassB.Companion.get()
其中, Companion能夠省略不寫:
ClassB.index ClassB.create() ClassB.get()
固然,咱們也能夠指定伴生對象的名稱:
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" } }
測試代碼:
ClassC.index ClassC.create()// com.easy.kotli.ClassC@7440e464,具體運行值會變化 ClassC.get() // Hi, I am CompanyC ClassC.CompanyC.index ClassC.CompanyC.create() ClassC.CompanyC.get()
伴生對象的初始化是在相應的類被加載解析時,與 Java 靜態初始化器的語義相匹配。
即便伴生對象的成員看起來像其餘語言的靜態成員,在運行時他們仍然是真實對象的實例成員。並且,還能夠實現接口:
interface BeanFactory<T> { fun create(): T } class MyClass { companion object : BeanFactory<MyClass> { override fun create(): MyClass { println("MyClass Created!") return MyClass() } } }
測試代碼:
MyClass.create() // "MyClass Created!" MyClass.Companion.create() // "MyClass Created!"
另外,若是想使用Java中的靜態成員和靜態方法的話,咱們能夠用:
@JvmField註解:生成與該屬性相同的靜態字段
@JvmStatic註解:在單例對象和伴生對象中生成對應的靜態方法
就像咱們爲何要用enum類型同樣,好比你有一個enum類型 MoneyUnit,定義了元、角、分這些單位。枚舉就是爲了控制住你全部要的狀況是正確的,而不是用硬編碼方式寫成字符串「元」,「角」,「分」。
一樣,sealed的目的相似,一個類之因此設計成sealed,就是爲了限制類的繼承結構,將一個值限制在有限集中的類型中,而不能有任何其餘的類型。
在某種意義上,sealed類是枚舉類的擴展:枚舉類型的值集合也是受限的,但每一個枚舉常量只存在一個實例,而密封類的一個子類能夠有可包含狀態的多個實例。
要聲明一個密封類,須要在類名前面添加 sealed 修飾符。密封類的全部子類都必須與密封類在同一個文件中聲明(在 Kotlin 1.1 以前, 該規則更加嚴格:子類必須嵌套在密封類聲明的內部):
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()
使用密封類的主要場景是在使用 when 表達式的時候,可以驗證語句覆蓋了全部狀況,而無需再添加一個 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` 子句,由於咱們已經覆蓋了全部的狀況 }
測試代碼:
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
val/var
在開始講數據類以前,咱們先來看一下幾種類聲明的寫法。
寫法一:
class Aook(name: String)
這樣寫,這個name變量是沒法被外部訪問到的。它對應的反編譯以後的Java代碼以下:
public final class Aook { public Aook(@NotNull String name) { Intrinsics.checkParameterIsNotNull(name, "name"); super(); } }
寫法二:
要想這個name變量被訪問到,咱們能夠在類體中再聲明一個變量,而後把這個構造函數中的參數賦值給它:
class Cook(name: String) { val name = name }
測試代碼:
val cook = Cook("Cook") cook.name
對應的Java實現代碼是:
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; } }
寫法三:
class Dook(val name: String) class Eook(var name: String)
構造函數中帶var、val修飾的變量,Kotlin編譯器會自動爲它們生成getter、setter函數。
上面的寫法對應的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; } }
測試代碼:
val dook = Dook("Dook") dook.name val eook = Eook("Eook") eook.name
下面咱們來學習一下Kotlin中的數據類: data class
。
咱們寫Java代碼的時候,會常常建立一些只保存數據的類。好比說:
等等。
這些咱們統稱爲領域模型中的實體類。最簡單的實體類是POJO類,含有屬性及屬性對應的set和get方法,實體類常見的方法還有用於輸出自身數據的toString方法。
data class
的概念在 Kotlin 中,也有對應這樣的領域實體類的概念,並在語言層面上作了支持,叫作數據類 :
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是必需要帶上的。由於編譯器要把主構造函數中聲明的全部屬性,自動生成如下函數:
equals()/hashCode() toString() : 格式是 User(name=Jacky, gender=Male, age=10) componentN() 函數 : 按聲明順序對應於全部屬性component1()、component2() ... copy() 函數
若是咱們自定義了這些函數,或者繼承父類重寫了這些函數,編譯器就不會再去生成。
測試代碼:
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)
在一些場景下,咱們須要複製一個對象來改變它的部分屬性,而其他部分保持不變。 copy() 函數就是爲此而生成。例如上面的的 User 類的copy函數的使用:
val olderJack = jack.copy(age = 2) val anotherJack = jack.copy(name = "Jacky", age = 10)
數據類有如下的限制要求:
1.主構造函數須要至少有一個參數。下面的寫法是錯誤的:
data class Gook // error, data class must have at least one primary constructor parameter
2.主構造函數的全部參數須要標記爲 val 或 var;
data class Hook(name: String)// error, data class must have only var/val property
跟普通類同樣,數據類也能夠有次級構造函數:
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.數據類不能是抽象、開放、密封或者內部的。也就是說,下面的寫法都是錯誤的:
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
數據類只能是final的:
final data class Mook(val name: String) // modifier abstract is incompatible with data
4.在1.1以前數據類只能實現接口。自 1.1 起,數據類能夠擴展其餘類。代碼示例:
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 } }
測試代碼:
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
能夠看出,因爲咱們重寫了override fun toString(): String
, 對應的輸出使咱們熟悉的類的輸出格式。
若是咱們不重寫這個toString函數,則會默認輸出:
LoginUser(name=Admin, password=admin)
上面的類聲明的構造函數,要求咱們每次必須初始化name、password的值,若是咱們想擁有一個無參的構造函數,咱們只要對全部的屬性指定默認值便可:
data class LoginUser(val name: String = "", val password: String = "") : DBase(), IBaseA, IBaseB { ... }
這樣咱們在建立對象的時候,就能夠直接使用:
val loginUser3 = LoginUser() loginUser3.name loginUser3.password
解構至關於 Component 函數的逆向映射:
val helen = User("Helen", "Female", 15) val (name, gender, age) = helen println("$name, $gender, $age years of age")
輸出:
Helen, Female, 15 years of age
Pair
和Triple
標準庫中的二元組 Pair類就是一個數據類:
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標準庫中,對Pair類還增長了轉換成List的擴展函數:
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)
類能夠嵌套在其餘類中,能夠嵌套多層:
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 } } } }
測試代碼:
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)
咱們能夠看出,訪問嵌套類的方式是直接使用 類名.
, 有多少層嵌套,就用多少層類名來訪問。
普通的嵌套類,沒有持有外部類的引用,因此是沒法訪問外部類的變量的:
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 } } } }
咱們在Nested類中,訪問不到Outer類中的變量zero,one。
若是想要訪問到,咱們只須要在Nested類前面加上inner
關鍵字修飾,代表這是一個嵌套的內部類。
類能夠標記爲 inner 以便可以訪問外部類的成員。內部類會帶有一個對外部類的對象的引用:
class NestedClassesDemo { class Outer { private val zero: Int = 0 val one: Int = 1 inner class Inner { fun accessOuter() = { println(zero) // works println(one) // works } } }
測試代碼:
val innerClass = NestedClassesDemo.Outer().Inner().accessOuter()
咱們能夠看到,當訪問inner class Inner
的時候,咱們使用的是Outer().Inner()
, 這是持有了Outer的對象引用。跟普通嵌套類直接使用類名訪問的方式區分。
匿名內部類,就是沒有名字的內部類。既然是內部類,那麼它天然也是能夠訪問外部類的變量的。
咱們使用對象表達式建立一個匿名內部類實例:
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() } } }
若是對象是函數式 Java 接口,即具備單個抽象方法的 Java 接口的實例,例如上面的例子中的Runnable接口:
@FunctionalInterface public interface Runnable { public abstract void run(); }
咱們可使用lambda表達式建立它,下面的幾種寫法都是能夠的:
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() }
測試代碼:
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
關於lambda表達式以及函數式編程,咱們將在下一章中學習。
代理模式,也稱委託模式。
在代理模式中,有兩個對象參與處理同一個請求,接受請求的對象將請求委託給另外一個對象來處理。代理模式是一項基本技巧,許多其餘的模式,如狀態模式、策略模式、訪問者模式本質上是在特殊的場合採用了代理模式。
代理模式使得咱們能夠用聚合來替代繼承,它還使咱們能夠模擬mixin(混合類型)。委託模式的做用是將委託者與實際實現代碼分離出來,以達成解耦的目的。
一個代理模式的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; //經過構造函數傳遞代理者 public JProxy(JSubject sub) { this.subject = sub; } @Override public void request() { //實現接口中定義的方法 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
就像支持單例模式的object對象同樣,Kotlin 在語言層面原生支持委託模式。
代碼示例:
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() }
在這個例子中,委託代理類 ProxySubject 繼承接口 Subject,並將其全部共有的方法委託給一個指定的對象sb :
class ProxySubject(val sb: Subject) : Subject by sb
ProxySubject 的超類型Subject中的 by sb
表示sb 將會在 ProxySubject 中內部存儲。
另外,咱們在覆蓋重寫了函數override fun hello()
。
測試代碼:
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
一般對於屬性類型,咱們是在每次須要的時候手動聲明它們:
class NormalPropertiesDemo { var content: String = "NormalProperties init content" }
那麼這個content屬性將會很「呆板」。屬性委託賦予了屬性富有變化的活力。
例如:
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
後面的表達式的Content()
就是該屬性 _委託_的對象。content屬性對應的 get()
(和 set()
)會被委託給Content()
的 operator fun getValue()
和 operator fun setValue()
函數,這兩個函數是必須的,並且得是操做符函數。
測試代碼:
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() 函數定義以下:
@kotlin.jvm.JvmVersion public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
它接受一個 lambda 並返回一個 Lazy <T> 實例的函數,返回的實例能夠做爲實現懶加載屬性的委託:
第一次調用 get() 會執行已傳遞給 lazy() 的 lamda 表達式並記錄下結果, 後續調用 get() 只是返回以前記錄的結果。
代碼示例:
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 !
默認狀況下,對於 lazy 屬性的求值是同步的(synchronized), 下面兩種寫法是等價的:
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 ! " })
該值是線程安全的。全部線程會看到相同的值。
若是初始化委託多個線程能夠同時執行,不須要同步鎖,使用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
函數,當屬性值被從新賦值的時候, 觸發其中的回調函數 onChange。
該函數定義以下:
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") }) }
測試代碼:
val ph = PostHierarchy() ph.level = "P1" ph.level = "P2" ph.level = "P3" println(ph.level) // P3
輸出:
P0 -> P1 P1 -> P2 P2 -> P3 P3
咱們能夠看出,屬性level
每次賦值,都回調了Delegates.observable
中的lambda表達式所寫的onChange
函數。
這個函數定義以下:
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) }
當咱們把屬性委託給這個函數時,咱們能夠經過onChange
函數的返回值是否爲true, 來選擇屬性的值是否須要改變。
代碼示例:
class PostHierarchy { var grade: String by Delegates.vetoable("T0", { property, oldValue, newValue -> true }) var notChangeGrade: String by Delegates.vetoable("T0", { property, oldValue, newValue -> false }) }
測試代碼:
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
咱們能夠看出,當onChange函數返回值是false的時候,對屬性notChangeGrade的賦值都沒有生效,依然是原來的默認值T0 。
咱們也可使用委託來實現屬性的非空限制:
var name: String by Delegates.notNull()
這樣name屬性就被限制爲不能爲null,若是被賦值null,編譯器直接報錯:
ph.name = null // error Null can not be a value of a non-null type String
咱們也能夠把屬性委託給Map:
class Account(val map: Map<String, Any?>) { val name: String by map val password: String by map }
測試代碼:
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 }
測試代碼:
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)
本章咱們介紹了Kotlin面向對象編程的特性: 類與構造函數、抽象類與接口、繼承以及多重繼承等基礎知識,同時介紹了Kotlin中的註解類、枚舉類、數據類、密封類、嵌套類、內部類、匿名內部類等特性類。最後咱們學習了Kotlin中對單例模式、委託模式的語言層面上的內置支持:object對象、委託。
總的來講,Kotlin相比於Java的面向對象編程,增長很多有趣的功能與特性支持,這使得咱們代碼寫起來更加方便快捷了。
咱們知道,在Java 8 中,引進了對函數式編程的支持:Lambda表達式、Function接口、stream API等,而在Kotlin中,對函數式編程的支持更加全面豐富,代碼寫起來也更加簡潔優雅。下一章中,咱們來一塊兒學習Kotlin的函數式編程。
本章示例代碼工程: