第4章 類與面向?qū)ο缶幊痰?章 類與面向?qū)ο缶幊?/a>

第4章 類與面向對象編程

在前面的章節(jié)中,我們學(xué)習(xí)了kotlin的語言基礎(chǔ)知識、類型系統(tǒng)等相關(guān)的知識。在本章節(jié)以及下一章中,我們將一起來學(xué)習(xí)kotlin對面向?qū)ο?/b>編程以及函數(shù)式編程的支持。

本章我們介紹Kotlin的面向?qū)ο缶幊獭?/p>

4.1 面向?qū)ο缶幊毯喪?/p>

50年代后期,在用FORTRAN語言編寫大型程序時,由于沒有封裝機(jī)制,那個時候的變量都是“全局變量”,那么就會不可避免的經(jīng)常出現(xiàn)變量名沖突問題。在ALGOL60中采用了以 Begin – End 為標(biāo)識的程序塊,使塊內(nèi)變量名是局部的,以避免它們與程序中塊外的同名變量相沖突。在編程語言中首次提供了封裝(保護(hù))的機(jī)制。此后,程序塊結(jié)構(gòu)廣泛用于Pascal 、Ada、C等高級語言之中。

60年代中后期,Simula語言在ALGOL基礎(chǔ)上研制開發(fā),它將ALGOL的塊結(jié)構(gòu)概念向前發(fā)展一步,提出了對象的概念,并使用了類,也支持類繼承。其后的發(fā)展簡史如下圖所示:

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

面向?qū)ο缶幊趟枷胫饕菑?fù)用性和靈活性(彈性)。復(fù)用性是面向?qū)ο缶幊痰囊粋€主要機(jī)制。靈活性主要是應(yīng)對變化的特性,因為客戶的需求是不斷改變的,怎樣適應(yīng)客戶需求的變化,這是軟件設(shè)計靈活性或者說是彈性的問題。

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

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

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

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

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

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

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

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

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

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

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

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

面向?qū)ο缶幊淌且环N編程思想,相比于早期的結(jié)構(gòu)化程序設(shè)計,抽象層次更高,思考解決問題的方式上也更加貼近人類的思維方式?,F(xiàn)代編程語言基本都支持面向?qū)ο缶幊谭妒健?/p>

計算機(jī)領(lǐng)域中的所有問題,都可以通過向上一層進(jìn)行抽象封裝來解決.這里的封裝的本質(zhì)概念,其實就是“映射”。從面向過程到面向?qū)ο螅俚皆O(shè)計模式,架構(gòu)設(shè)計,面向服務(wù),sass/Pass/Iass等等的思想,各種軟件理論思想五花八門,但萬變不離其宗——

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

面向?qū)ο缶幊痰囊袁F(xiàn)實世界中的事物(對象)為中心來思考, 認(rèn)識問題, 并根據(jù)這些事物的本質(zhì)特征, 把它們抽象表示為系統(tǒng)中的類。其核心思想可以用下圖簡要說明:

第4章 類與面向?qū)ο缶幊痰?章 類與面向?qū)ο缶幊?></figure>
<p>面向?qū)ο缶幊?/p>
<p>面向?qū)ο缶幊袒陬惥幊?,更加貼近人類解決問題的習(xí)慣方法。讓軟件世界更像現(xiàn)實世界。面向?qū)ο缶幊掏ㄟ^抽象出關(guān)鍵的問題域來分解系統(tǒng)。對象不僅能表示具體的事物,還能表示抽象的規(guī)則、計劃或<a href=事件。關(guān)于面向?qū)ο缶幊痰暮诵牡母拍钊缦聢D所示

第4章 類與面向?qū)ο缶幊痰?章 類與面向?qū)ο缶幊?></figure>
<p>面向?qū)ο缶幊痰暮诵牡母拍?/p>
<p>4.2  聲明類</p>
<p>本節(jié)介紹Kotlin中類和<a href=構(gòu)造函數(shù)的聲明。

4.2.1 空類

使用class關(guān)鍵字聲明類。我們可以聲明一個什么都不干的類

代碼語言:JavaScript代碼運行次數(shù):0運行復(fù)制

class AnEmptyClassfun main(args: Array<string>) {    val anEmptyClass = AnEmptyClass() // Kotlin中不需要使用new    println(anEmptyClass)    println(anEmptyClass is AnEmptyClass) // 對象實例是AnEmptyClass類型    println(anEmptyClass::class)}</string>

輸出

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

com.easy.kotlin.AnEmptyClass@2626b418trueclass com.easy.kotlin.AnEmptyClass (Kotlin reflection is not available)

4.2.2 聲明類和構(gòu)造函數(shù)

在Kotlin中, 我們可以在聲明類的時候同時聲明構(gòu)造函數(shù),語法格式是在類的后面使用括號包含構(gòu)造函數(shù)的參數(shù)列表

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

class Person(var name: String, var age: Int, var sex: String) { // 聲明類和構(gòu)造函數(shù)    override fun toString(): String { // override關(guān)鍵字,重寫toString()        return "Person(name='$name', age=$age, sex='$sex')"    }}

使用這樣的簡潔語法,可以通過主構(gòu)造器來定義屬性并初始化屬性值(這里的屬性值可以是var或val)。

在代碼中這樣使用Person類

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

val person = Person("Jack", 29, "M")println("person = ${person}")

輸出

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

person = Person(name='Jack', age=29, sex='M')

另外,我們也可以先聲明屬性,等到構(gòu)造實例對象的時候再去初始化屬性值,那么我們的Person類可以聲明如下

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

class Person1 {    lateinit var name: String // lateinit 關(guān)鍵字表示該屬性延遲初始化    var age: Int = 0  // lateinit 關(guān)鍵字不能修飾 primitive 類型    lateinit var sex: String    override fun toString(): String {        return "Person1(name='$name', age=$age, sex='$sex')"    }}

我們可以在代碼中這樣創(chuàng)建Person1的實例對象

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

    val person1 = Person1()    person1.name = "Jack"    person1.age = 29    person1.sex = "M"    println("person1 = ${person1}")

輸出

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

person1 = Person1(name='Jack', age=29, sex='M')

如果我們想聲明一個具有多種構(gòu)造方式的類,可以使用 constructor 關(guān)鍵字聲明構(gòu)造函數(shù),示例代碼如下

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

class Person2() { // 無參的主構(gòu)造函數(shù)    lateinit var name: String    var age: Int = 0    lateinit var sex: String    constructor(name: String) : this() { // this 關(guān)鍵字指向當(dāng)前類對象實例        this.name = name    }    constructor(name: String, age: Int) : this(name) {        this.name = name        this.age = age    }    constructor(name: String, age: Int, sex: String) : this(name, age) {        this.name = name        this.age = age        this.sex = sex    }    override fun toString(): String {        return "Person1(name='$name', age=$age, sex='$sex')"    }}

上面的寫法,總體來看也有些樣板代碼,其實在idea中,我們寫上面的代碼,只需要寫下面的3行,剩下的就交給IDEA自動生成了

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

class Person2 {    lateinit var name: String    var age: Int = 0    lateinit var sex: String}

自動生成構(gòu)造函數(shù)的操作示意圖

1.在當(dāng)前類中“右擊”鼠標(biāo)操作,選擇Generate (在Mac上的快捷鍵是 Command + N)

第4章 類與面向?qū)ο缶幊痰?章 類與面向?qū)ο缶幊?></figure>
<p>右擊鼠標(biāo)操作</p>
<p>點擊之后,跳出對話框:生成次級構(gòu)造函數(shù)</p>
<figure><img decoding=運行復(fù)制

constructor()

選擇一個 name 屬性,生成

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

    constructor(name: String) {        this.name = name    }

選擇name,age屬性生成

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

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

3個屬性都選擇,生成

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

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

最后,我們可以在代碼中這樣創(chuàng)建Person2的實例對象

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

    val person21 = Person2()    person21.name = "Jack"    person21.age = 29    person21.sex = "M"    println("person21 = ${person21}")    val person22 = Person2("Jack", 29)    person22.sex = "M"    println("person22 = ${person22}")    val person23 = Person2("Jack", 29, "M")    println("person23 = ${person23}")

實際上,我們在編程實踐中用到最多的構(gòu)造函數(shù),還是這個

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

class Person(var name: String, var age: Int, var sex: String)

而當(dāng)確實需要通過比較復(fù)雜的邏輯來構(gòu)建一個對象的時候,可采用構(gòu)建者(Builder)模式來實現(xiàn)。

4.3 抽象類與接口

抽象類表示“is-a”的關(guān)系,而接口所代表的是“has-a”的關(guān)系。

抽象類用來表征問題領(lǐng)域的抽象概念。所有編程語言都提供抽象機(jī)制。機(jī)器語言是對機(jī)器的模仿抽象,匯編語言是對機(jī)器語言的高層次抽象,高級語言(Fortran,C,Basic等)是對匯編的高層次抽象。而我們這里所說的面向?qū)ο缶幊陶Z言是對過程函數(shù)的高層次封裝。這個過程如下圖所示

第4章 類與面向?qū)ο缶幊痰?章 類與面向?qū)ο缶幊?></figure>
<p>編程語言的抽象機(jī)制</p>
<p>抽象類和接口是Kotlin語言中兩種不同的抽象概念,他們的存在對<a href=多態(tài)提供了非常好的支持。這個機(jī)制跟Java相同。

4.3.1 抽象類與抽象成員

抽象是相對于具象而言。例如設(shè)計一個圖形編輯軟件,問題領(lǐng)域中存在著長方形(Rectangle)、圓形(Circle)、三角形(Triangle)等這樣一些具體概念,它們是具象。但是它們又都屬于形狀(Shape)這樣一個抽象的概念。它們的關(guān)系如下圖所示

第4章 類與面向?qū)ο缶幊痰?章 類與面向?qū)ο缶幊?></figure>
<p>形狀Shape的抽象繼承關(guān)系</p>
<p>對應(yīng)的Kotlin代碼如下</p>
<p>代碼語言:javascript<i></i>代碼運行次數(shù):0<svg xmlns=運行復(fù)制

package com.easy.kotlinabstract class Shapeclass Rectangle : Shape() // 繼承類的語法是使用冒號 : , 父類需要在這里使用構(gòu)造函數(shù)初始化class Circle : Shape()class Triangle : Shape()

因為抽象的概念在問題領(lǐng)域中沒有對應(yīng)的具體概念,所以抽象類是不能夠?qū)嵗?。下面的代碼編譯器會報錯

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

val s = Shape() // 編譯不通過!不能實例化抽象類

我們只能實例化它的繼承子類。代碼示例如下

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

val r = Rectangle()println(r is Shape) // true

現(xiàn)在我們有了抽象類,但是沒有成員。通常一個類的成員有屬性和函數(shù)。抽象類的成員也必須是抽象的,需要使用abstract 關(guān)鍵字修飾。下面我們聲明一個抽象類Shape,并帶有width ,heigth,radius屬性和 area() 函數(shù), 代碼如下

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

abstract class Shape {    abstract var width: Double    abstract var heigth: Double    abstract var radius: Double    abstract fun area(): Double}

這個時候,繼承抽象類Shape的方法如下

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

class Rectangle(override var width: Double, override var heigth: Double, override var radius: Double) : Shape() { // 聲明類的同時也聲明了構(gòu)造函數(shù)    override fun area(): Double {        return heigth * width    }}class Circle(override var width: Double, override var heigth: Double, override var radius: Double) : Shape() {    override fun area(): Double {        return 3.14 * radius * radius    }}

其中,override 是覆蓋寫父類屬性和函數(shù)的關(guān)鍵字。

在代碼中這樣調(diào)用具體實現(xiàn)的類的函數(shù)

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

fun main(args: Array<string>) {    val r = Rectangle(3.0, 4.0, 0.0)    println(r.area()) // 12.0    val c = Circle(0.0, 0.0, 4.0)    println(c.area()) // 50.24}</string>

抽象類中可以有帶實現(xiàn)的函數(shù),例如我們在抽象類Shape中添加一個函數(shù)onClick()

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

abstract class Shape {    ...    fun onClick() { // 默認(rèn)是final的,不可被覆蓋重寫        println("I am Clicked!")    }}

那么,我們在所有的子類中都可以直接調(diào)用這個onClick()函數(shù)

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

    val r = Rectangle(3.0, 4.0, 0.0)    r.onClick() // I am Clicked!    val c = Circle(0.0, 0.0, 4.0)    c.onClick() // I am Clicked!

父類Shape中的onClick()函數(shù)默認(rèn)是final的,不可被覆蓋重寫。如果想要開放給子類重新實現(xiàn)這個函數(shù),我們可以在前面加上open 關(guān)鍵字

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

abstract class Shape {    ...    open fun onClick() {        println("I am Clicked!")    }}

在子類中這樣覆蓋重寫

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

class Rectangle(override var width: Double, override var heigth: Double, override var radius: Double) : Shape() {    override fun area(): Double {        return heigth * width    }    override fun onClick(){        println("${this::class.simpleName} is Clicked!")    }}fun main(args: Array<string>) {    val r = Rectangle(3.0, 4.0, 0.0)    println(r.area())    r.onClick()}</string>

其中,this::class.simpleName 是Kotlin中的反射的API,在gradle工程的build.gradle中需要添加依賴 compile “org.jetbrains.kotlin:kotlin-reflect:$kotlin_version” ,我們將在后面的章節(jié)中詳細(xì)介紹。

上面的代碼運行輸出

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

12.0Rectangle is Clicked!

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

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

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

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

4.3.2 接口

接口是一種比抽象類更加抽象的“類”。接口本身代表的是一種“類型”的概念。但在語法層面,接口本身不是類,不能實例化接口,我們只能實例化它的實現(xiàn)類。

接口是用來建立類與類之間的協(xié)議。實現(xiàn)該接口的實現(xiàn)類必須要實現(xiàn)該接口的所有方法。在Java 8 和Kotlin中,接口可以實現(xiàn)一些通用的方法。

接口是抽象類的延伸,Kotlin跟Java一樣,不支持同時繼承多個父類,也就是說繼承只能存在一個父類(單繼承)。但是接口不同,一個類可以同時實現(xiàn)多個接口(多組合),不管這些接口之間有沒有關(guān)系。這樣可以實現(xiàn)多重繼承。

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

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

interface ProjectService

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

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

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

接口是沒有構(gòu)造函數(shù)的。我們使用冒號: 語法來實現(xiàn)一個接口,如果有多個用,逗號隔開:

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

class ProjectServiceImpl : ProjectService // 跟繼承抽象類語法一樣,使用冒號class ProjectMilestoneServiceImpl : ProjectService, MilestoneService // 實現(xiàn)多個接口使用逗號( ,) 隔開

在重寫print()函數(shù)時,因為我們實現(xiàn)的ProjectService、MilestoneService都有一個print()函數(shù),當(dāng)我們直接使用super.print()時,編譯器是無法知道我們想要調(diào)用的是那個里面的print函數(shù)的,這個我們叫做覆蓋沖突,如下圖所示

第4章 類與面向?qū)ο缶幊痰?章 類與面向?qū)ο缶幊?></figure>
<p>覆蓋沖突</p>
<p>這個時候,我們可以使用下面的語法來調(diào)用:</p>
<p>代碼語言:javascript<i></i>代碼運行次數(shù):0<svg xmlns=運行復(fù)制

super<projectservice>.print()super<milestoneservice>.print()</milestoneservice></projectservice>

4.4 Object對象

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

Kotlin中沒有 靜態(tài)屬性和方法,但是可以使用關(guān)鍵字 object 聲明一個object 單例對象:

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

package com.easy.kotlinobject User {    val username: String = "admin"    val password: String = "admin"    fun hello() {        println("Hello, object !")    }}fun main(args: Array<string>) {    println(User.username) // 跟Java的靜態(tài)類一樣的調(diào)用形式    println(User.password)    User.hello()}</string>

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

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

class DataProcessor {    companion object DataProcessor {        fun process() {            println("I am processing data ...")        }    }}fun main(args: Array<string>) {    DataProcessor.process() // I am processing data ...}</string>

一個類只能有1個伴生對象。

4.5 數(shù)據(jù)類

顧名思義,數(shù)據(jù)類就是只存儲數(shù)據(jù),不包含操作行為的類。Kotlin的數(shù)據(jù)類可以為我們節(jié)省大量樣板代碼(Java 中強(qiáng)制我們要去寫一getter、setter,而實際上這些方法都是“不言自明”的),這樣最終代碼更易于理解和便于維護(hù)。

使用關(guān)鍵字為 data class 創(chuàng)建一個只包含數(shù)據(jù)的類:

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

data class LoginUser(val username: String, val password: String)

在IDEA中提供了方便的Kotlin工具箱,我們可以把上面的代碼反編譯成等價的Java代碼。步驟如下

1.菜單欄選擇:Tools -> Kotlin -> Show Kotlin Bytecode

第4章 類與面向?qū)ο缶幊痰?章 類與面向?qū)ο缶幊?></figure>
<p>菜單欄選擇:Tools -> Kotlin -> Show Kotlin Bytecode</p>
<p>點擊Decompile</p>
<figure><img decoding=運行復(fù)制

public final class LoginUser {   @NotNull   private final String username;   @NotNull   private final String password;   @NotNull   public final String getUsername() {      return this.username;   }   @NotNull   public final String getPassword() {      return this.password;   }   public LoginUser(@NotNull String username, @NotNull String password) {      Intrinsics.checkParameterIsNotNull(username, "username");      Intrinsics.checkParameterIsNotNull(password, "password");      super();      this.username = username;      this.password = password;   }   @NotNull   public final String component1() {      return this.username;   }   @NotNull   public final String component2() {      return this.password;   }   @NotNull   public final LoginUser copy(@NotNull String username, @NotNull String password) {      Intrinsics.checkParameterIsNotNull(username, "username");      Intrinsics.checkParameterIsNotNull(password, "password");      return new LoginUser(username, password);   }   // $FF: synthetic method   // $FF: bridge method   @NotNull   public static LoginUser copy$default(LoginUser var0, String var1, String var2, int var3, Object var4) {      if ((var3 &amp; 1) != 0) {         var1 = var0.username;      }      if ((var3 &amp; 2) != 0) {         var2 = var0.password;      }      return var0.copy(var1, var2);   }   public String toString() {      return "LoginUser(username=" + this.username + ", password=" + this.password + ")";   }   public int hashCode() {      return (this.username != null ? this.username.hashCode() : 0) * 31 + (this.password != null ? this.password.hashCode() : 0);   }   public boolean equals(Object var1) {      if (this != var1) {         if (var1 instanceof LoginUser) {            LoginUser var2 = (LoginUser)var1;            if (Intrinsics.areEqual(this.username, var2.username) &amp;&amp; Intrinsics.areEqual(this.password, var2.password)) {               return true;            }         }         return false;      } else {         return true;      }   }}

編譯器會從主構(gòu)造函數(shù)中聲明的屬性,自動創(chuàng)建以下函數(shù):

equals() / hashCode() 函數(shù)toString() 格式為”LoginUser(username=” + this.username + “, password=” + this.password + “)”component1(),component2() 函數(shù)返回對應(yīng)下標(biāo)的屬性值,按聲明順序排列copy() 函數(shù): 根據(jù)舊對象屬性重新 new LoginUser(username, password) 一個對象出來

如果這些函數(shù)在類中已經(jīng)被明確定義了,或者從超類中繼承而來,編譯器就不再生成。

數(shù)據(jù)類有如下限制:

主構(gòu)造函數(shù)至少包含一個參數(shù)參數(shù)必須標(biāo)識為val 或者 var不能為 abstract, open, sealed 或者 inner不能繼承其它類 (但可以實現(xiàn)接口)

另外,數(shù)據(jù)類可以在解構(gòu)聲明中使用:

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

package com.easy.kotlindata class LoginUser(val username: String, val password: String)fun main(args: Array<string>) {    val loginUser = LoginUser("admin", "admin")    val (username, password) = loginUser    println("username = ${username}, password = ${password}") // username = admin, password = admin}</string>

Kotlin 標(biāo)準(zhǔn)庫提供了 Pair 和 Triple數(shù)據(jù)類 。

4.6 注解

注解是將元數(shù)據(jù)附加到代碼中。元數(shù)據(jù)信息由注解 kotlin.Metadata定義。

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

@Retention(AnnotationRetention.RUNTIME)@Target(AnnotationTarget.CLASS)internal annotation class Metadata

這個@Metadata信息存在于由 Kotlin 編譯器生成的所有類文件中, 并由編譯器和反射讀取。例如,我們使用Kotlin聲明一個注解

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

annotation class Suspendable // Java中使用的是@interface Suspendable

那么,編譯器會生成對應(yīng)的元數(shù)據(jù)信息

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

@Retention(RetentionPolicy.RUNTIME)@Metadata(   mv = {1, 1, 7},   bv = {1, 0, 2},   k = 1,   d1 = {"u0000nnu0002u0018u0002nu0002u0010u001bnu0000bu0086u0002u0018u00002u00020u0001Bu0000¨u0006u0002"},   d2 = {"Lcom/easy/kotlin/Suspendable;", "", "production sources for module kotlin_tutorials_main"})public @interface Suspendable {}

Kotlin 的注解完全兼容 Java 的注解。例如,我們在Kotlin中使用Spring Data Jpa

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

interface ImageRepository : PagingAndSortingRepository<image long> {       @Query("SELECT a from #{#entityName} a where a.isDeleted=0 and a.isFavorite=1 and a.category like %:searchText% order by a.gmtModified desc")    fun searchFavorite(@Param("searchText") searchText: String, pageable: Pageable): Page<image>    @Throws(Exception::class)    @Modifying    @Transactional    @Query("update #{#entityName} a set a.isFavorite=1,a.gmtModified=now() where a.id=?1")    fun addFavorite(id: Long)}</image></image>

用起來跟Java的注解基本一樣。再舉個Kotlin使用Spring mvc注解的代碼實例

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

@Controllerclass MeituController {    @Autowired    lateinit var imageRepository: ImageRepository    @RequestMapping(value = *arrayOf("/", "meituView"), method = arrayOf(RequestMethod.GET))    fun meituView(model: Model, request: HttpServletRequest): ModelAndView {        model.addAttribute("requestURI", request.requestURI)        return ModelAndView("meituView")    }}

從上面的例子,我們可以看出Kotlin使用java框架非常簡單方便。

4.7 枚舉

Kotlin中使用 enum class 關(guān)鍵字來聲明一個枚舉類。例如

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

enum class Direction {    NORTH, SOUTH, WEST, EAST // 每個枚舉常量都是一個對象, 用逗號分隔}

相比于字符串常量,使用枚舉能夠?qū)崿F(xiàn)類型安全。枚舉類有兩個內(nèi)置的屬性:

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

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

分別表示的是枚舉對象的值跟下標(biāo)位置。例如上面的Direction枚舉類,它的枚舉對象的信息如下

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

&gt;&gt;&gt; val north = Direction.NORTH&gt;&gt;&gt; north.nameNORTH&gt;&gt;&gt; north.ordinal0&gt;&gt;&gt; north is Directiontrue

每一個枚舉都是枚舉類的實例,它們可以被初始化:

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

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

枚舉Color的枚舉對象的信息如下

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

&gt;&gt;&gt; val c = Color.GREEN&gt;&gt;&gt; cGREEN&gt;&gt;&gt; c.rgb65280&gt;&gt;&gt; c.ordinal1&gt;&gt;&gt; c.nameGREEN

4.8 內(nèi)部類4.8.1 普通嵌套類

Kotlin中,類可以嵌套。一個類可以嵌套在其他類中,而且可以嵌套多層。

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

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            }        }    }}

測試代碼:

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

    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()

我們可以看出,代碼中 NestedClassesDemo.Outer.Nested().getTwo() 訪問嵌套類的方式是直接使用 類名.來訪問, 有多少層嵌套,就用多少層類名來訪問。

普通的嵌套類,沒有持有外部類的引用,所以是無法訪問外部類的變量的:

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

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            }        }}}

4.8.2 嵌套內(nèi)部類

如果一個類Inner想要訪問外部類Outer的成員,可以在這個類前面添加修飾符 inner。內(nèi)部類會帶有一個對外部類的對象的引用:

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

package com.easy.kotlinclass NestedClassesDemo {    class Outer {        private val zero: Int = 0        val one: Int = 1        inner class Inner {            fun accessOuter() = {                println(zero) // works                println(one) // works            }        }    }}fun main(args: Array<string>) {    val innerClass = NestedClassesDemo.Outer().Inner().accessOuter()}</string>

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

4.8.3 匿名內(nèi)部類

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

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

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

class NestedClassesDemo {    class AnonymousInnerClassDemo {        var isRunning = false        fun doRun() {            Thread(object : Runnable { // 匿名內(nèi)部類                override fun run() {                    isRunning = true                    println("doRun : i am running, isRunning = $isRunning")                }            }).start()        }    }}

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

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

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

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

代碼語言:javascript代碼運行次數(shù):0運行復(fù)制

            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()            }

更多關(guān)于Lambda表達(dá)式以及函數(shù)式編程相關(guān)內(nèi)容,我們將在下一章節(jié)中介紹。

本章小結(jié)

本章我們介紹了Kotlin面向?qū)ο缶幊痰奶匦裕?類與構(gòu)造函數(shù)、抽象類與接口、繼承與組合等知識,同時介紹了Kotlin中的注解類、枚舉類、數(shù)據(jù)類、嵌套類、內(nèi)部類、匿名內(nèi)部類、單例object對象等特性類。

總的來說,在面向?qū)ο缶幊谭妒降闹С稚希琄otlin相比于Java增加不少有趣的功能與特性支持,這使得我們代碼寫起來更加方便快捷了。

我們知道,在Java 8 中,引進(jìn)了對函數(shù)式編程的支持:Lambda表達(dá)式、Function接口、stream API等,而在Kotlin中,對函數(shù)式編程的支持更加全面豐富,代碼寫起來也更加簡潔優(yōu)雅。下一章中,我們來一起學(xué)習(xí)Kotlin的函數(shù)式編程。

? 版權(quán)聲明
THE END
喜歡就支持一下吧
點贊8 分享