《Kotlin 程序設(shè)計(jì)》第五章 Kotlin 面向?qū)ο缶幊蹋∣OP)

第五章 Kotlin 面向?qū)ο缶幊蹋∣OP)

1. 面向?qū)ο蟮?code>HelloWorld

開篇我們來看一個(gè)OOP版本的HelloWorld:

package com.easy.kotlin
class Greeter(
val name: String//  屬性
) {

    //  行為
    fun greet() {
        println("Hello, ${name}");
    }
}

fun main(args: Array<String>) {
    Greeter("Jack").greet() // Kotlin創(chuàng)建對(duì)象,不再使用`new` keyword
}

Kotlin 同Java、 Scala、Groovy 一樣,都使用關(guān)鍵字class 來定義類。

新建一個(gè)類的實(shí)例無需像 Java 一樣使用 new 關(guān)鍵字,直接調(diào)用構(gòu)造函數(shù)Greeter("Jack") 即可。

Kotlin 文件名以.kt為后綴,源代碼文件中可以定義多個(gè)類。Greeter.kt經(jīng)過編譯之后的類文件默認(rèn)命名是文件名加上Kt結(jié)尾,即為GreeterKt.class

2. 面向?qū)ο缶幊趟枷牒?jiǎn)述

在20世紀(jì)60年代,軟件曾出現(xiàn)過嚴(yán)重危機(jī),由軟件錯(cuò)誤而引起的信息丟失、系統(tǒng)報(bào)廢事件屢有發(fā)生。為此,1968年,荷蘭E.W.Dijkstra提出了程序設(shè)計(jì)中常用的GOTO語句的三大危害:

破壞了程序的動(dòng)靜一致性;
程序不易測(cè)試;
限制了代碼優(yōu)化。

此舉引起了軟件界長(zhǎng)達(dá)數(shù)年的論戰(zhàn),并由此產(chǎn)生了結(jié)構(gòu)化程序設(shè)計(jì)方法,同時(shí)誕生了基于這一設(shè)計(jì)方法的程序設(shè)計(jì)語言Pascal。

由瑞士Niklaus Wirth開發(fā)的Pascal,具備優(yōu)秀的數(shù)據(jù)結(jié)構(gòu)和控制結(jié)構(gòu),為程序員提供了極大的方便性與靈活性,大受歡迎。筆者中學(xué)時(shí)候,第一門啟蒙語言就是Pascal。至今還清晰記得那臺(tái)式屏幕上藍(lán)色的Turbo Pascal界面,閃爍著白色的代碼的場(chǎng)景。

結(jié)構(gòu)化程序設(shè)計(jì)思想采用了模塊分解與功能抽象和自頂向下、分而治之的方法,從而有效地將一個(gè)較復(fù)雜的程序系統(tǒng)設(shè)計(jì)任務(wù)分解成許多易于控制和處理的子程序,便于開發(fā)和維護(hù)。因此,結(jié)構(gòu)化方法迅速走紅,并在整個(gè)20世紀(jì)70年代的軟件開發(fā)中占絕對(duì)統(tǒng)治地位。

但是,到了70年代末期,隨著計(jì)算機(jī)科學(xué)的發(fā)展和應(yīng)用領(lǐng)域的不斷擴(kuò)大,對(duì)計(jì)算機(jī)技術(shù)的要求越來越高。結(jié)構(gòu)化程序設(shè)計(jì)語言和結(jié)構(gòu)化分析與設(shè)計(jì)已無法滿足用戶需求的變化,于是面向?qū)ο缶幊蹋∣OP)技術(shù)隨之而來。 面向?qū)ο蟪绦蛟O(shè)計(jì)在未來的軟件開發(fā)領(lǐng)域引起了大的變革,極大地提高了軟件開發(fā)的效率。

面向?qū)ο笳Z言借鑒了20世紀(jì)50年代的人工智能語言LISP,引入了動(dòng)態(tài)綁定的概念和交互式開發(fā)環(huán)境的思想;始于20世紀(jì)60 年代的離散事件模擬語言SIMULA67,引入了類和繼承。于20世紀(jì)70年代的Smalltalk逐漸發(fā)展成熟。Java借鑒了SmallTalk,統(tǒng)治了互聯(lián)網(wǎng)開發(fā)領(lǐng)域的大片江山。

面向?qū)ο缶幊趟枷耄菫榱私鉀Q現(xiàn)實(shí)問題而應(yīng)運(yùn)而生的。面向?qū)ο缶幊淌且环N自頂向下的程序設(shè)計(jì)方法.萬事萬物都是對(duì)象,對(duì)象有其行為(方法), 狀態(tài)(成員變量,屬性)。

所謂“類”和“對(duì)象”,對(duì)應(yīng)過程式語言(例如,C語言)里面的結(jié)構(gòu)體(struct),本質(zhì)是一個(gè)邏輯抽象的代碼 “映射”(map)(一切皆是映射)。

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

從面向過程到面向?qū)ο螅俚皆O(shè)計(jì)模式,架構(gòu)設(shè)計(jì),面向服務(wù),各種軟件理論五花八門,但萬變不離其宗——你要解決一個(gè)怎樣的問題?你對(duì)這個(gè)世界的本質(zhì)認(rèn)知是怎樣的?你的業(yè)務(wù)領(lǐng)域的邏輯問題,流程等等。

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

人的生命只有一次。生命太短暫,所以不要去做一些重復(fù)無聊的事情。能交給計(jì)算機(jī)做的,就盡量交給計(jì)算機(jī)去做。此乃編程的濫觴之地。

縱覽整個(gè)計(jì)算機(jī)的發(fā)展史,最重要的思想非“抽象”莫屬。

一層層的抽象封裝了實(shí)現(xiàn)的細(xì)節(jié),計(jì)算機(jī)開疆?dāng)U土,南征北戰(zhàn),發(fā)展到了今天蔚為壯觀的互聯(lián)網(wǎng),云計(jì)算,大數(shù)據(jù),機(jī)器智能的時(shí)代。

同時(shí),也使得程序員寫代碼,從最初的拿著符號(hào)表在紙袋上打孔,到使用近似自然語言的高級(jí)程序設(shè)計(jì)語言來編程,以及當(dāng)今各種庫,api,框架,集成開發(fā)工具集,智能化的編碼提示,代碼生成等等技術(shù),使得我們現(xiàn)在程序員,能更多的去關(guān)注問題本身以及邏輯的實(shí)現(xiàn)。

從只有少數(shù)技術(shù)人會(huì)用的命令行的操作系統(tǒng)unix、dos,到人性化的GUI圖形界面操作系統(tǒng),以及移動(dòng)互聯(lián)網(wǎng)時(shí)代的智能設(shè)備,計(jì)算機(jī)越來越融入到人類生活的方方面面。

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

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

計(jì)算機(jī)中的所有問題 , 都可以通過向上抽象封裝一層來解決。

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

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

我們最早使用機(jī)器碼01來控制電平高低,進(jìn)而控制數(shù)字電路操作寄存器,CPU等嘗試著表達(dá)思想中的邏輯, 控制硬件計(jì)算和顯示, 發(fā)現(xiàn)是可行的。

后來,我們把其中的最常用的操作步驟,進(jìn)一步封裝抽象成CPU指令集映射,于是誕生了匯編助記符語言——比機(jī)器指令更容易記憶;

再接著, 創(chuàng)造了編譯器、解釋器和計(jì)算機(jī)高級(jí)語言。通過匯編語言的向上抽象封裝一層編譯器,于是有了pascal,fortran,C語言;在犧牲少量性能的情況下, 獲得比匯編語言更強(qiáng)且更容易使用的語句控制能力:條件、分支、循環(huán), 以及更多的語言特性: 指針、結(jié)構(gòu)體、聯(lián)合體、枚舉等, 還創(chuàng)造了函數(shù), 能夠?qū)⒁幌盗兄噶罘庋b成一個(gè)獨(dú)立的邏輯塊反復(fù)使用;再對(duì)核心函數(shù)api進(jìn)行封裝形成開發(fā)包(Development Kit) 。

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

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

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

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

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

縱觀計(jì)算機(jī)編程發(fā)展史,人類的大腦不斷抽象、努力封裝一個(gè)又一個(gè)邏輯體系——使得我們逐漸能夠以更加友好、更加自然的方式去編寫程序。

我們知道,編程的本質(zhì)就是在創(chuàng)造世界。當(dāng)然,這是一個(gè)虛擬的世界。是人類大腦對(duì)我們真實(shí)的世界的邏輯映射。既然是一個(gè)世界,就必然會(huì)有存在(對(duì)象,數(shù)據(jù)結(jié)構(gòu)), 以及無限可能變化的運(yùn)動(dòng)(算法,方法,函數(shù))

不管是面向?qū)ο螅ù嬖冢┚幊蹋€是函數(shù)式(運(yùn)動(dòng)(算法,方法,函數(shù)))編程,都是我們?nèi)祟惔竽X對(duì)我們現(xiàn)實(shí)世界的問題的解決方案過程中,所建立的思維模型。模型畢竟還是模型,不可能裝下全部的真實(shí)的世界。正是有像01陰陽 這種形而上的哲學(xué)概念,才有了世界的無數(shù)種可能。

以上算是一些關(guān)于編程的粗淺的思考。好了,下面言歸正傳,進(jìn)入正題。

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

3.1 聲明類

Kotlin使用關(guān)鍵字*class *聲明類

class Book {
}

這個(gè)類聲明被花括號(hào)包圍,包括

  • 類名
  • 類head頭(指定其類型參數(shù),主構(gòu)造函數(shù)等)
  • 類body。

類頭和主干都是可選的。如果這個(gè)類沒有body,花括號(hào)可以被省略。

class Empty

3.2 類修飾符

open 修飾符

Kotlin 默認(rèn)會(huì)為每個(gè)變量和方法添加 final 修飾符。也就是說,在 Kotlin 中默認(rèn)每個(gè)類都是不可被繼承的。這么做的目的是為了程序運(yùn)行的性能。

其實(shí)在 Java 程序中,你也應(yīng)該盡可能為每個(gè)類添加final 修飾符( 見 《Effective Java 》第四章 17 條)。

如果你確定這個(gè)類是會(huì)被繼承的,那么你需要給這個(gè)類添加 open 修飾符。

internal 修飾符

Java 有三種訪問修飾符,public/private/protected。沒有修飾符,是默認(rèn)的包級(jí)別訪問權(quán)限。

在 Kotlin 中,有private、protected、internal以及 public等四種修飾符,它們可用于修飾類、對(duì)象、接口、構(gòu)造器、函數(shù)、屬性、以及屬性的set方法等。默認(rèn)的訪問權(quán)限是 public。其中 internal,是模塊級(jí)別的訪問權(quán)限。

模塊(module)是指一起編譯的一組 Kotlin 源代碼文件:

  • 一個(gè) IntelliJ IDEA 模塊
  • 一個(gè) Maven 工程, 或 Gradle 工程
  • 通過 Ant 任務(wù)的一次調(diào)用編譯的一組文件

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

在Kotlin中的類可以有主構(gòu)造函數(shù)(Primary Constructor)和一個(gè)或多個(gè)二級(jí)構(gòu)造函數(shù)(Secondary Constructor)。在 Scala 中稱為 Main ConstructorSlave Constructor,Kotlin換了個(gè)名字,意思基本相同。

主構(gòu)造函數(shù)是類頭的一部分, 代碼示例如下:

class Book1 constructor(val name:String, val author:String){

}

如果這個(gè)主構(gòu)造函數(shù)沒有任何注解或者可見的修飾符,這個(gè)constructor關(guān)鍵字可以被省略

class Book2 (val name:String, val author:String){

}

主構(gòu)造方法的參數(shù)可以聲明為 val 或 var ,使用方法與其聲明為成員變量時(shí)相同。

這個(gè)主構(gòu)造函數(shù)不能包含任何的代碼。不過,初始化的代碼可以被放置在initializer blocks(初始的語句塊),以init為前綴作為關(guān)鍵字。該語句塊中的所有可執(zhí)行語句都屬于主構(gòu)造器,在對(duì)象被創(chuàng)建時(shí)都會(huì)被調(diào)用。

class Book2 (val name:String, val author:String){
    init {
        println("Book2 initialized with value (name= ${name}, author=${author})")
        name = name.toUpperCase()
    }
}

完整代碼示例:

package com.easy.kotlin

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

class Empty


class Book1 constructor(val name:String, val author:String){
    override fun toString(): String {
        return "Book1(name='$name', author='$author')"
    }
}

class Book2 (var name:String, val author:String){
    init {
        println("Book2 initialized with value (name= ${name}, author=${author})")
        name = name.toUpperCase()
    }

    override fun toString(): String {
        return "Book2(name='$name', author='$author')"
    }


}

fun main(args:Array<String>){
    println(Empty())
    println(Book1("Easy Kotlin", "Jack"))
    println(Book2("Easy Kotlin", "Jack"))
}

運(yùn)行輸出:

com.easy.kotlin.Empty@4b1210ee
Book1(name='Easy Kotlin', author='Jack')
Book2 initialized with value (name= Easy Kotlin, author=Jack)
Book2(name='EASY KOTLIN', author='Jack')

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

類也可以擁有被稱為"二級(jí)構(gòu)造函數(shù)"(實(shí)現(xiàn)多個(gè)構(gòu)造函數(shù)),通常被加上前綴"constructor"。

1、次構(gòu)造函數(shù)不能有聲明 val 或 var
2、如果類有一個(gè)主構(gòu)造函數(shù)(無論有無參數(shù)),每個(gè)次構(gòu)造函數(shù)需要直接或間接委托給主構(gòu)造函數(shù)(即調(diào)用主構(gòu)造方法或其它次構(gòu)造函數(shù)),用this關(guān)鍵字來調(diào)用。

class Person {

    var name: String = ""
    var age: Int = 0

    constructor() {
        println("constructor 1 called!")

    }

    constructor(name: String) : this() {
        println("constructor 2 called!")
        this.name = name

    }

    constructor(name: String, age: Int) : this(name) {
        println("constructor 3 called!")
        this.name = name
        this.age = age
    }

    override fun toString(): String {
        return "Person(name='$name', age=$age)"
    }

}


fun main(args: Array<String>) {
    println(Person())
    println(Person("Jack"))
    println(Person("Jack",29))
}


運(yùn)行輸出:

constructor 1 called!
Person(name='', age=0)
constructor 1 called!
constructor 2 called!
Person(name='Jack', age=0)
constructor 1 called!
constructor 2 called!
constructor 3 called!
Person(name='Jack', age=29)

我們可以看出,調(diào)用構(gòu)造函數(shù)Person("Jack",29) 創(chuàng)建對(duì)象,編譯器通過層層向上委托,最終完成該對(duì)象的創(chuàng)建。

構(gòu)造函數(shù)傳參

fun main(args: Array<String>) {
    val pair = Pair(1, "one")

    val (num, name) = pair

    println("num = $num, name = $name")
    
    val triple = Triple(10,"B",10.0)
    val (a,b,c) = triple
    println("a=$a, b=$b, c=$c")
}

class Pair<K, V>(val first: K, val second: V) {
    operator fun component1(): K {
        return first
    }

    operator fun component2(): V {
        return second
    }
}

class Triple<K,V,T>(val first: K,val second:V,val third:T){
    operator fun component1():K{return first}
    operator fun component2():V{return second}
    operator fun component3():T{return third}
}




運(yùn)行結(jié)果:

num = 1, name = one
a=10, b=B, c=10.0

3.4 創(chuàng)建類的實(shí)例

要?jiǎng)?chuàng)建一個(gè)類的實(shí)例,我們只要像普通的函數(shù)那樣調(diào)用其構(gòu)造函數(shù)即可:

val person = Person("Jack",29)

Kotlin中,不再使用new關(guān)鍵字

類成員

類可以包括

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

3.5 繼承

在Kotlin所有的類中都有一個(gè)共同的父類Any(這跟Scala一樣),這是一個(gè)默認(rèn)的open根父類。我們看一下Any類的源碼吧:

package kotlin

/**
 * The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass.
 */
public open class Any {
    /**
     * Indicates whether some other object is "equal to" this one. Implementations must fulfil the following
     * requirements:
     *
     * * Reflexive: for any non-null reference value x, x.equals(x) should return true.
     * * Symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
     * * Transitive:  for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true
     * * Consistent:  for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
     *
     * Note that the `==` operator in Kotlin code is translated into a call to [equals] when objects on both sides of the
     * operator are not null.
     */
    public open operator fun equals(other: Any?): Boolean

    /**
     * Returns a hash code value for the object.  The general contract of hashCode is:
     *
     * * Whenever it is invoked on the same object more than once, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified.
     * * If two objects are equal according to the equals() method, then calling the hashCode method on each of the two objects must produce the same integer result.
     */
    public open fun hashCode(): Int

    /**
     * Returns a string representation of the object.
     */
    public open fun toString(): String
}

仔細(xì)閱讀注釋部分的內(nèi)容,我們基本就能知道這個(gè)Any是啥了。

我們使用Kotlin編程,聲明的所有類,都默認(rèn)繼承這個(gè)Any類。

class Example // Implicitly inherits from Any

Kotlin中所有的類默認(rèn)都是不可繼承的(final),所以我們只能繼承那些明確聲明open或者abstract的類。

當(dāng)我們只有單個(gè)構(gòu)造器時(shí),我們需要在從父類繼承下來的構(gòu)造器中指定需要的參數(shù)。

代碼示例:


open class Person {

    var name: String = ""
    var age: Int = 0

    constructor() {
        println("constructor 1 called!")

    }

    constructor(name: String) : this() {
        println("constructor 2 called!")
        this.name = name

    }

    constructor(name: String, age: Int) : this(name) {
        println("constructor 3 called!")
        this.name = name
        this.age = age
    }

    override fun toString(): String {
        return "Person(name='$name', age=$age)"
    }

    fun sayHi() {
        val name = this.name
        println("${name} say Hi to u!")
        val b = Book("Easy Kotlin")
        println(b)
    }


    class Book {
        var name: String = ""

        constructor() {

        }

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

        override fun toString(): String {
            return "Book(name='$name')"
        }


    }

}

class Student : Person {
    var id: String = ""
    var sex: String = ""

    constructor() {
    }

    constructor(id: String, sex: String) : super() {
        this.id = id
        this.sex = sex
    }

    constructor(name: String, id: String, sex: String) : super(name) {
        this.id = id
        this.sex = sex
    }

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

    override fun toString(): String {
        return "Student(id='$id', sex='$sex', name=${super.name}, age = ${super.age})"
    }


}

我們使用super.name, super.age來調(diào)用父類中的屬性字段。

3.6 接口和抽象類

Kotlin接口使用interface關(guān)鍵字。Kotlin 的接口類似于 Java 8。可以包含抽象方法,以及方法的實(shí)現(xiàn)。和抽象類不同的是,接口不能保存狀態(tài);可以有屬性但必須是抽象的 或 提供訪問實(shí)現(xiàn)。

Kotlin抽象類使用abstract關(guān)鍵字聲明。

Kotlin中的繼承抽象類,實(shí)現(xiàn)接口的處理方式,跟Java一樣,采用“單繼承,多實(shí)現(xiàn)”的方式。代碼示例如下:


abstract class A {
    abstract fun fa()
    abstract fun f()
}

interface B {
    fun fb() {
        print("FB")
    }

    fun f() {
        print("B")
    }
}

class C : A, B {
    override fun fa() {
    }

    override fun fb() {
    }

    override fun f() {
    }

    constructor() {

    }


}

我們直接使用class C : A, B這樣的寫法。比Java中使用extends, implements要簡(jiǎn)潔。

接口和抽象類的函數(shù),默認(rèn)是open的。我們可以不用標(biāo)注。

另外,我們可以重寫一個(gè) open 非抽象類的open函數(shù),得到一個(gè)抽象類的抽象函數(shù)。

open class Base {
  open fun f() {}
}

abstract class Derived : Base() {
  override abstract fun f()
}

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

package com.easy.kotlin

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

interface Clickable {
    fun click()
}
class Button : Clickable {
    override fun click() = println("I was clicked")
}

fun main(args: Array<String>) {
    Button().click()
}


Kotlin中實(shí)現(xiàn)接口,使用冒號(hào):關(guān)鍵字標(biāo)識(shí)。實(shí)現(xiàn)類前使用override 關(guān)鍵字修飾。

3.8 override重寫覆蓋父類函數(shù)

Kotlin追求簡(jiǎn)潔顯式的風(fēng)格。

Kotlin在繼承父類并覆蓋父類函數(shù)時(shí),要求父類必須有open標(biāo)注,被覆蓋的函數(shù)必須有open標(biāo)注,并且子類的函數(shù)必須加override標(biāo)注:

open class Base {
    open fun v() {}
    fun notopenv() {}
}

class Derived : Base() {
    override fun v() {}
//    override fun notopenv(){}// 'notopenv' in 'Base' is final, so it cannot be overridden
//    fun notopenv() {} // 這樣寫也是不允許的
}

Derived.v()函數(shù)上必須加上override標(biāo)注。如果沒寫,編譯器將會(huì)報(bào)錯(cuò)。

如果父類的這個(gè)函數(shù)

open fun v() {}

沒有標(biāo)注open,則子類中不允許定義同名函數(shù),不論加不加override

成員標(biāo)記為override的本身是開放的,也就是說,它可以在子類中重寫。如果你想禁止重寫,使用final關(guān)鍵字:

open class AnotherDerived() : Base() {
  final override fun v() {}
}

3.9 使用伴生對(duì)象聲明靜態(tài)類和方法

Kotlin中的伴生對(duì)象(companion objects),應(yīng)用的場(chǎng)景就是Java或C#單例類。伴生對(duì)象里面的函數(shù),對(duì)應(yīng)的就是靜態(tài)方法。

代碼示例:

java代碼

package com.restfeel.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;

@Configuration
@PropertySource(value = {"classpath:common.properties"})
public class PropertyConfig {

    public PropertyConfig() {}

    @Bean
    public static PropertySourcesPlaceholderConfigurer myPropertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    /**
     * Properties to support the 'test' mode of operation.
     */
    @Configuration
    @Profile({"devlopment", "default"})
    @PropertySource(value = {"classpath:env-development.properties"})
    static class Dev {
    }

    /**
     * Properties to support the 'test' mode of operation.
     */
    @Configuration
    @Profile("test")
    @PropertySource(value = {"classpath:env-test.properties"})
    static class Test {
    }

    /**
     * Properties to support the 'production' mode of operation.
     */
    @Configuration
    @Profile("production")
    @PropertySource(value = {"classpath:env-production.properties"})
    static class Production {
        // Define additional beans for this profile here
    }

}

對(duì)應(yīng)的kotlin代碼

package com.restfeel.config

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.context.annotation.PropertySource
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer

/**
 * Created by jack on 2017/3/29.
 */

@Configuration
@PropertySource(value = *arrayOf("classpath:common.properties"))
class ApplicationConfig {

    @Bean
    fun myPropertySourcesPlaceholderConfigurer(): PropertySourcesPlaceholderConfigurer {
        return PropertySourcesPlaceholderConfigurer();
    }

    //靜態(tài)類,伴生對(duì)象
    companion object {
        /**
         * Properties to support the 'test' mode of operation.
         */
        @Configuration
        @Profile(*arrayOf("devlopment", "default"))
        @PropertySource(value = *arrayOf("classpath:env-development.properties"))
        class Dev {
        }

        /**
         * Properties to support the 'test' mode of operation.
         */
        @Configuration
        @Profile("test")
        @PropertySource(value = *arrayOf("classpath:env-test.properties"))
        class Test {
        }

        /**
         * Properties to support the 'production' mode of operation.
         */
        @Configuration
        @Profile("production")
        @PropertySource(value = *arrayOf("classpath:env-production.properties"))
        class Production {
            // Define additional beans for this profile here
        }
    }


}

我們使用

//靜態(tài)類,伴生對(duì)象
companion object {}

來聲明靜態(tài)類和方法。

3.10 嵌套類Nested Class

類可以嵌套在其他類中:

class Outer {
    private val bar: Int = 1

    class Nested {
        fun foo1() = 2
        //fun foo11() = bar// 訪問不到
    }

}

fun main(args: Array<String>) {
    val nestedDemo = Outer.Nested().foo1() // 2
    println(nestedDemo)
}

但是,這兩個(gè)類Outer, Nested 對(duì)象成員之間不能直接訪問。比如說,

    class Nested {
        fun foo1() = 2
        //fun foo11() = bar// 訪問不到
    }

Nested里面的函數(shù)foo11 是不能直接使用Outer的成員變量bar的。要想訪問外部類的成員變量,我們可以使用內(nèi)部類。

3.11 內(nèi)部類Inner Class

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

class Outer {
    private val bar: Int = 1

    class Nested {
        fun foo1() = 2
        //fun foo11() = bar// 訪問不到
    }

    inner class Inner {
        fun foo2() = bar // inner class 能夠訪問外部類的成員
    }
}

fun main(args: Array<String>) {
    val nestedDemo = Outer.Nested().foo1() // 2
    val innerDemo = Outer().Inner().foo2() // 1

    println(nestedDemo)
    println(innerDemo)
}

3.12 使用Kotlin的對(duì)象表達(dá)式創(chuàng)建匿名內(nèi)部類

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

window.addMouseListener(object: MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ……
    }
                                                                                                            
    override fun mouseEntered(e: MouseEvent) {
        // ……
    }
})

如果對(duì)象是函數(shù)式 Java 接口(即具有單個(gè)抽象方法的 Java 接口)的實(shí)例,
我們可以直接使用lambda表達(dá)式創(chuàng)建它:

val listener = ActionListener { println("clicked") }

多個(gè)超類型可以由跟在冒號(hào)后面的逗號(hào)分隔的列表指定:

open class AA(x: Int) {
    open val y: Int = x
}

interface BB {}

val ab: AA = object : AA(1), BB {
    override val y = 100
}

如果我們只需要“一個(gè)對(duì)象”,那么我們可以簡(jiǎn)單地寫:

    fun adHocf() {
        val adHoc = object {
            var x: Int = 0
            var y: Int = 0
        }

        println("adHoc.x + adHoc.y = " + (adHoc.x + adHoc.y))
    }

完整代碼示例:

package com.easy.kotlin

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

class Outer {
    private val bar: Int = 1

    class Nested {
        fun foo1() = 2
        //fun foo11() = bar// 訪問不到
    }

    inner class Inner {
        fun foo2() = bar // inner class 能夠訪問外部類的成員
    }
}


open class AA(x: Int) {
    open val y: Int = x
}

interface BB {}

val ab: AA = object : AA(1), BB {
    override val y = 100
}


class  Hoc{
    fun adHocf() {
        val adHoc = object {
            var x: Int = 0
            var y: Int = 0
        }

        println("adHoc.x + adHoc.y = " + (adHoc.x + adHoc.y))
    }
}



fun main(args: Array<String>) {
    val nestedDemo = Outer.Nested().foo1()
    val innerDemo = Outer().Inner().foo2()

    println(nestedDemo)// 2
    println(innerDemo)// 1

    val h = Hoc()
    println(h.adHocf())
    val a = AA(1)
    println(a.y) //1
    println(ab.y) // 15
}



3.13 數(shù)據(jù)類data class

data 修飾的類稱之為數(shù)據(jù)類。它通常用在我們寫的一些 POJO 類上。

代碼示例:

data class User(val name: String, val age: Int)

這個(gè)對(duì)應(yīng)我們寫的Java的Bean類。這些類有如下基本方法:

  • equals()/ hashCode() 函數(shù)
  • toString() 默認(rèn)格式是: "User(name=John, age=42)",
  • componentN() 函數(shù) corresponding to the properties in their order of declaration,
  • copy() 函數(shù)

其中,componentN()函數(shù)是通過屬性位置來直接訪問屬性值。

完整代碼示例:

package com.easy.kotlin

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

data class User(val name: String, val id: Int, val password: String)


fun main(args: Array<String>) {
    val user = getUser()
    println("name = ${user.name}, id = ${user.id}, password = ${user.password}")

    val (name, id) = getUser()
    println("name = $name, id = $id")
    println("name = ${getUser().component1()}, id = ${getUser().component2()}")
}

fun getUser(): User {
    return User("Alex", 1, "123456")
}

數(shù)據(jù)庫實(shí)體類bean例子

package jason.chen.mini_springboot.restful.entity

import java.util.*
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id

@Entity
class Customer(
        val firstName: String,
        val lastName: String,
        val gmtCreated: Date,
        val gmtModified: Date,
        val isDeleted: Int, //1 Yes 0 No
        val deletedDate:Date,
        @Id @GeneratedValue(strategy = GenerationType.AUTO)
        val id: Long = -1) {
    override fun toString(): String {
        return "Customer(firstName='$firstName', lastName='$lastName', gmtCreated=$gmtCreated, gmtModified=$gmtModified, isDeleted=$isDeleted, deletedDate=$deletedDate, id=$id)"
    }
}




data class Shop(val name: String, val customers: List<Customer>)

data class Customer(val name: String, val city: City, val orders: List<Order>) {
    override fun toString() = "$name from ${city.name}"
}

data class Order(val products: List<Product>, val isDelivered: Boolean)

data class Product(val name: String, val price: Double) {
    override fun toString() = "'$name' for $price"
}

data class City(val name: String) {
    override fun toString() = name
}



3.14 建造者模式:構(gòu)建一個(gè)對(duì)象

定義Rectangle對(duì)象,代碼如下:

package geometry.shapes

import java.util.Random

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() = height == width
}

fun createRandomRectangle(): Rectangle {
    val random = Random()
    return Rectangle(random.nextInt(), random.nextInt())
}


3.15 封裝一個(gè)日期工具類

package jason.chen.mini_springboot.restful.utils

import java.text.SimpleDateFormat
import java.util.*

/**
 * Created by jack on 2017/3/11.
 * @author jack
 * @date 2017/03/11
 *
 *  val date = Date()
    date + 1 //后一天
    date - 1 //前一天
    date + Month(2) //后2月
    date - Year(3) //前3年
    date++  //本月的最后一天
    date--  //本月的第一天
    取年月日時(shí)分秒 date[0]  date[1] date[2] 。。。

    //日期比較
    if( date1 > date2){

    }

 */


enum class DateOptUnit {
    YEAR,MONTH,DATE;
    fun parseType():Int{
        var value = Calendar.DATE
        when(this){
            YEAR -> value = Calendar.DATE
            MONTH -> value = Calendar.MONTH
            DATE ->  value = Calendar.DATE
        }
        return value
    }
}

data class DateOperator(val unit :DateOptUnit,val value: Int)

fun Any.year(value:Int):DateOperator {
    return DateOperator(DateOptUnit.YEAR,value)
}

fun Any.month(value:Int):DateOperator {
    return DateOperator(DateOptUnit.MONTH,value)
}

fun Any.day(value:Int):DateOperator {
    return DateOperator(DateOptUnit.DATE,value)
}

/**
 * date+1
 * 往后的幾天
 */
operator fun Date.plus(nextVal:Int):Date{
    val calendar = GregorianCalendar()
    calendar.time = this
    calendar.add(Calendar.DATE, nextVal)
    return calendar.time
}

/**
 * date-1
 */
operator fun Date.minus(nextVal:Int):Date{
    val calendar = GregorianCalendar()
    calendar.time = this
    calendar.add(Calendar.DATE, nextVal*-1)
    return calendar.time
}

/**
 * date+year(3)
 * 往后的幾天
 */
operator fun Date.plus(nextVal:DateOperator):Date{
    val calendar = GregorianCalendar()
    calendar.time = this
    calendar.add(nextVal.unit.parseType(), nextVal.value)
    return calendar.time
}

/**
 * date-month(4)
 */
operator fun Date.minus(nextVal:DateOperator):Date{
    val calendar = GregorianCalendar()
    calendar.time = this
    calendar.add(nextVal.unit.parseType(), nextVal.value*-1)
    return calendar.time
}

/**
 * 得到月末
 */
operator fun Date.inc():Date {
    val calendar = GregorianCalendar()
    calendar.time = this
    calendar.add(Calendar.MONTH, 1);
    calendar.set(Calendar.DAY_OF_MONTH, 0);
    return calendar.time
}

/**
 * 得到月初
 */
operator fun Date.dec():Date {
    val calendar = GregorianCalendar()
    calendar.time = this
    calendar.set(Calendar.DAY_OF_MONTH, 1)
    return calendar.time
}

/**
 * 取 年月日時(shí)分秒 0 - 5
 * 例如 2015-12-21 22:15:56
 * date[0]:2015  date[1]:12 date[2]:21
 */
operator fun Date.get(position:Int):Int {
    val calendar = GregorianCalendar()
    calendar.time = this
    var value = 0
    when(position) {
        0 -> value = calendar.get(Calendar.YEAR)
        1 -> value = calendar.get(Calendar.MONTH)+1
        2 -> value = calendar.get(Calendar.DAY_OF_MONTH)
        3 -> value = calendar.get(Calendar.HOUR)
        4 -> value = calendar.get(Calendar.MINUTE)
        5 -> value = calendar.get(Calendar.SECOND)
    }
    return value
}

/**
 * 比較2個(gè)日期
 * if(date1 > date2) {
 * }
 */

operator fun Date.compareTo(compareDate : Date):Int {
    return (time - compareDate.time).toInt()
}

/**
 * 日期轉(zhuǎn)化為字符串
 */
fun Date.stringFormat(formatType:String):String{
    return SimpleDateFormat(formatType).format(this)
}



3.16 枚舉類

在 Kotlin 中,每個(gè)枚舉常量都是一個(gè)對(duì)象。枚舉常量用逗號(hào)分隔。 代碼示例:

package jason.chen.mini_springboot.restful.config

/**
 * Created by jack on 2017/6/1.
 */
enum class KotlinBin(val binPath: String) {
    KOTLINC("src/main/resources/kotlinc/bin/kotlinc "),
    KOTLIN("src/main/resources/kotlinc/bin/kotlin ")
}

代碼這樣調(diào)用

package jason.chen.mini_springboot.restful.service

import jason.chen.mini_springboot.restful.config.KotlinBin
import org.springframework.stereotype.Service
import java.io.File

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

@Service
class KotlincService {

    fun kotlinc(ktFile:String){
        val file = File(".")
        file.listFiles().forEach(::println)
        val kotlinc = KotlinBin.KOTLINC.binPath + ktFile
        val runtime: Runtime = Runtime.getRuntime()
        val process: Process = runtime.exec(kotlinc)
        val exitValue = process.waitFor()
        if (exitValue != 0) {
            println("exit with $exitValue")
            return
        }

        process.inputStream.bufferedReader().lines().forEach {
            println(it)
        }
    }
    fun kotlin(ktFile:String):String{
        kotlinc(ktFile)

        val ktClass = ktFile.substring(0, ktFile.indexOf(".kt")) + "Kt"

        val kotlin = KotlinBin.KOTLIN.binPath + ktClass
        val runtime: Runtime = Runtime.getRuntime()
        val process: Process = runtime.exec(kotlin)
        val exitValue = process.waitFor()
        if (exitValue != 0) {
            println("exit with $exitValue")
            return "exit with $exitValue"
        }

        var result=""
        process.inputStream.bufferedReader().lines().forEach {
            println(it)
            result+=it
        }
        return result
    }
}

3.17 sealed 密封類

sealed 修飾的類稱為密封類,用來表示受限的類層次結(jié)構(gòu)。例如當(dāng)一個(gè)值為有限集中的 類型、而不能有任何其他類型時(shí)。在某種意義上,他們是枚舉類的擴(kuò)展:枚舉類型的值集合也是受限的,但每個(gè)枚舉常量只存在一個(gè)實(shí)例,而密封類的一個(gè)子類可以有可包含狀態(tài)的多個(gè)實(shí)例。

小結(jié)

參考文檔:

https://github.com/kymjs/KotlinDoc-cn/blob/master/unit3/ClassesInheritance.md


Kotlin開發(fā)者社區(qū)

專注分享 Java、 Kotlin、Spring/Spring Boot、MySQL、redis、neo4j、NoSQL、Android、JavaScript、React、Node、函數(shù)式編程、編程思想、"高可用,高性能,高實(shí)時(shí)"大型分布式系統(tǒng)架構(gòu)設(shè)計(jì)主題。

High availability, high performance, high real-time large-scale distributed system architecture design

分布式框架:Zookeeper、分布式中間件框架等
分布式存儲(chǔ):GridFS、FastDFS、TFS、MemCache、redis等
分布式數(shù)據(jù)庫:Cobar、tddl、Amoeba、Mycat
云計(jì)算、大數(shù)據(jù)、AI算法
虛擬化、云原生技術(shù)
分布式計(jì)算框架:MapReduce、Hadoop、Storm、Flink等
分布式通信機(jī)制:Dubbo、RPC調(diào)用、共享遠(yuǎn)程數(shù)據(jù)、消息隊(duì)列等
消息隊(duì)列MQ:Kafka、MetaQ,RocketMQ
怎樣打造高可用系統(tǒng):基于硬件、軟件中間件、系統(tǒng)架構(gòu)等一些典型方案的實(shí)現(xiàn):HAProxy、基于Corosync+Pacemaker的高可用集群套件中間件系統(tǒng)
Mycat架構(gòu)分布式演進(jìn)
大數(shù)據(jù)Join背后的難題:數(shù)據(jù)、網(wǎng)絡(luò)、內(nèi)存和計(jì)算能力的矛盾和調(diào)和
Java分布式系統(tǒng)中的高性能難題:AIO,NIO,Netty還是自己開發(fā)框架?
高性能事件派發(fā)機(jī)制:線程池模型、Disruptor模型等等。。。

合抱之木,生于毫末;九層之臺(tái),起于壘土;千里之行,始于足下。不積跬步,無以至千里;不積小流,無以成江河。

Kotlin 簡(jiǎn)介

Kotlin是一門非研究性的語言,它是一門非常務(wù)實(shí)的工業(yè)級(jí)編程語言,它的使命就是幫助程序員們解決實(shí)際工程實(shí)踐中的問題。使用Kotlin 讓 Java程序員們的生活變得更好,Java中的那些空指針錯(cuò)誤,浪費(fèi)時(shí)間的冗長(zhǎng)的樣板代碼,啰嗦的語法限制等等,在Kotlin中統(tǒng)統(tǒng)消失。Kotlin 簡(jiǎn)單務(wù)實(shí),語法簡(jiǎn)潔而強(qiáng)大,安全且表達(dá)力強(qiáng),極富生產(chǎn)力。

Java誕生于1995年,至今已有23年歷史。當(dāng)前最新版本是 Java 9。在 JVM 生態(tài)不斷發(fā)展繁榮的過程中,也誕生了Scala、Groovy、Clojure 等兄弟語言。

Kotlin 也正是 JVM 家族中的優(yōu)秀一員。Kotlin是一種現(xiàn)代語言(版本1.0于2016年2月發(fā)布)。它最初的目的是像Scala那樣,優(yōu)化Java語言的缺陷,提供更加簡(jiǎn)單實(shí)用的編程語言特性,并且解決了性能上的問題,比如編譯時(shí)間。 JetBrains在這些方面做得非常出色。

Kotlin語言的特性

用 Java 開發(fā)多年以后,能夠嘗試一些新的東西真是太棒了。如果您是 Java 開發(fā)人員,使用 Kotlin 將會(huì)非常自然流暢。如果你是一個(gè)Swift開發(fā)者,你將會(huì)感到似曾相識(shí),比如可空性(Nullability)。 Kotlin語言的特性有:

1.簡(jiǎn)潔

大幅減少樣板代碼量。

2.與Java的100%互操作性

Kotlin可以直接與Java類交互,反之亦然。這個(gè)特性使得我們可以直接重用我們的代碼庫,并將其遷移到 Kotlin中。由于Java的互操作性幾乎無處不在。我們可以直接訪問平臺(tái)API以及現(xiàn)有的代碼庫,同時(shí)仍然享受和使用 Kotlin 的所有強(qiáng)大的現(xiàn)代語言功能。

3.擴(kuò)展函數(shù)

Kotlin 類似于 C# 和 Gosu, 它提供了為現(xiàn)有類提供新功能擴(kuò)展的能力,而不必從該類繼承或使用任何類型的設(shè)計(jì)模式 (如裝飾器模式)。

4.函數(shù)式編程

Kotlin 語言一等支持函數(shù)式編程,就像Scala一樣。具備高階函數(shù)、Lambda 表達(dá)式等函數(shù)式基本特性。

5.默認(rèn)和命名參數(shù)

在Kotlin中,您可以為函數(shù)中的參數(shù)設(shè)置一個(gè)默認(rèn)值,并給每個(gè)參數(shù)一個(gè)名稱。這有助于編寫易讀的代碼。

6.強(qiáng)大的開發(fā)工具支持

而由于是JetBrains出品,我們擁有很棒的IDE支持。雖然Java到Kotlin的自動(dòng)轉(zhuǎn)換并不是100% OK 的,但它確實(shí)是一個(gè)非常好的工具。使用 IDEA 的工具轉(zhuǎn)換Java代碼為 Kotlin 代碼時(shí),可以輕松地重用60%-70%的結(jié)果代碼,而且修改成本很小。

Kotlin 除了簡(jiǎn)潔強(qiáng)大的語法特性外,還有實(shí)用性非常強(qiáng)的API以及圍繞它構(gòu)建的生態(tài)系統(tǒng)。例如:集合類 API、IO 擴(kuò)展類、反射API 等。同時(shí) Kotlin 社區(qū)也提供了豐富的文檔和大量的學(xué)習(xí)資料,還有在線REPL。

A modern programming language that makes developers happier. Open source forever

圖來自《Kotlin從入門到進(jìn)階實(shí)戰(zhàn)》 (陳光劍,清華大學(xué)出版社)
圖來自《Kotlin從入門到進(jìn)階實(shí)戰(zhàn)》 (陳光劍,清華大學(xué)出版社)

https://kotlinlang.org/

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

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