kotlin入門中文教程(2)

本文已收錄至學習筆記大全:JavaKotlinAndroidGuidejava

做者:leavesCgit

[TOC]github

十5、擴展函數和擴展屬性

15.一、擴展函數

擴展函數用於爲一個類增長一種新的行爲,這是爲缺乏有用函數的類進行擴展的途徑。擴展函數的用途就相似於在 Java 中實現的靜態工具方法。而在 kotlin 中使用擴展函數的一個優點就是咱們不須要在調用方法的時候把整個對象看成參數傳入,擴展函數表現得就像是屬於這個類自己的同樣,可使用 this 關鍵字並直接調用其全部 public 方法設計模式

擴展函數並不容許你打破它的封裝性,和在類內部定義的方法不一樣的是,擴展函數不能訪問私有的或是受保護的成員數組

//爲 String 類聲明一個擴展函數 lastChar() ,用於返回字符串的最後一個字符
//get方法是 String 類的內部方法,length 是 String 類的內部成員變量,在此處能夠直接調用
fun String.lastChar() = get(length - 1)

//爲 Int 類聲明一個擴展函數 doubleValue() ,用於返回其兩倍值
//this 關鍵字表明瞭 Int 值自己
fun Int.doubleValue() = this * 2
複製代碼

以後,咱們就能夠像調用類自己內部聲明的方法同樣,直接調用擴展函數安全

fun main() {
    val name = "leavesC"
    println("$name lastChar is: " + name.lastChar())

    val age = 24
    println("$age doubleValue is: " + age.doubleValue())
}
複製代碼

若是須要聲明一個靜態的擴展函數,則必須將其定義在伴生對象上,這樣就能夠在沒有 Namer 實例的狀況下調用其擴展函數,就如同在調用 Java 的靜態函數同樣bash

class Namer {

    companion object {

        val defaultName = "mike"

    }

}

fun Namer.Companion.getName(): String {
    return defaultName
}

fun main() {
    Namer.getName()
}
複製代碼

須要注意的是,若是擴展函數聲明於 class 內部,則該擴展函數只能該類和其子類內部調用,由於此時至關於聲明瞭一個非靜態函數,外部沒法引用到。因此通常都是將擴展函數聲明爲全局函數閉包

15.二、擴展屬性

擴展函數也能夠用於屬性app

//擴展函數也能夠用於屬性
//爲 String 類新增一個屬性值 customLen
var String.customLen: Int
    get() = length
    set(value) {
        println("set")
    }

fun main() {
    val name = "leavesC"
    println(name.customLen)
    name.customLen = 10
    println(name.customLen)
    //7
    //set
    //7
}
複製代碼

15.三、不可重寫的擴展函數

看如下例子,子類 Button 重寫了父類 View 的 click() 函數,此時若是聲明一個 View 變量,並賦值爲 Button 類型的對象,調用的 click() 函數將是 Button 類重寫的方法less

fun main() {
    val view: View = Button()
    view.click() //Button clicked
}

open class View {
    open fun click() = println("View clicked")
}

class Button : View() {
    override fun click() = println("Button clicked")
}
複製代碼

對於擴展函數來講,與以上的例子卻不同。若是基類和子類都分別定義了一個同名的擴展函數,此時要調用哪一個擴展函數是由變量的靜態類型來決定的,而非這個變量的運行時類型

fun main() {
    val view: View = Button()
    view.longClick() //View longClicked
}

open class View {
    open fun click() = println("View clicked")
}

class Button : View() {
    override fun click() = println("Button clicked")
}

fun View.longClick() = println("View longClicked")

fun Button.longClick() = println("Button longClicked")
複製代碼

此外,若是一個類的成員函數和擴展函數有相同的簽名,成員函數會被優先使用

擴展函數並非真正地修改了原來的類,其底層實際上是以靜態導入的方式來實現的。擴展函數能夠被聲明在任何一個文件中,所以有個通用的實踐是把一系列有關的函數放在一個新建的文件裏

須要注意的是,擴展函數不會自動地在整個項目範圍內生效,若是須要使用到擴展函數,須要進行導入

15.四、可空接收者

能夠爲可空的接收者類型定義擴展,即便接受者爲 null,使得開發者在調用擴展函數前沒必要進行判空操做,且能夠經過 this == null 來檢查接收者是否爲空

fun main() {
    var name: String? = null
    name.check() //this == null
    name = "leavesC"
    name.check() //this != null
}

fun String?.check() {
    if (this == null) {
        println("this == null")
        return
    }
    println("this != null")
}
複製代碼

十6、Lambda 表達式

Lambda 表達式本質上就是能夠傳遞給其它函數的一小段代碼,經過 Lambda 表達式能夠把通用的代碼結構抽取成庫函數,也能夠把 Lambda 表達式存儲在一個變量中,把這個變量當作普通函數對待

//因爲存在類型推導,因此如下三種聲明方式都是徹底相同的
    val plus1: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
    val plus2: (Int, Int) -> Int = { x, y -> x + y }
    val plus3 = { x: Int, y: Int -> x + y }
    println(plus3(1, 2))
複製代碼
  1. 一個 Lambda 表達式始終用花括號包圍,經過箭頭把實參列表和函數體分開
  2. 若是 Lambda 聲明瞭函數類型,那麼就能夠省略函數體的類型聲明
  3. 若是 Lambda 聲明瞭參數類型,且返回值支持類型推導,那麼就能夠省略函數類型聲明

雖說傾向於儘可能避免讓 Lambda 表達式引用外部變量以免反作用,但有些狀況下讓 Lambda 引用外部變量也能夠簡化計算結構。訪問了外部環境變量的 Lambda 表達式稱之爲閉包,閉包能夠被當作參數傳遞或者直接使用。與 Java 不一樣,kotlin 中的閉包不只能夠訪問外部變量也能夠對其進行修改

例如,假設咱們須要一個計算總和的方法,每次調用函數時都返回當前的總和大小。方法外部不提供保存當前總和的變量,由 Lambda 表達式內部進行存儲

fun main() {
    val sum = sumFunc()
    println(sum(10)) //10
    println(sum(20)) //30
    println(sum(30)) //60
}

fun sumFunc(): (Int) -> Int {
    var base = 0
    return fun(va: Int): Int {
        base += va
        return base
    }
}
複製代碼

此外,kotlin 也支持一種自動運行的語法

{ va1: Int, va2: Int -> println(va1 + va2) }(10, 20)
複製代碼

Lambda 表達式最多見的用途就是和集合一塊兒工做,看如下例子

要從一我的員列表中取出年齡最大的一位

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

fun main() {
    val people = listOf(Person("leavesC", 24), Person("Ye", 22))
    println(people.maxBy { it.age }) //Person(name=leavesC, age=24)
}
複製代碼

當中,庫函數 maxBy 能夠在任何集合上調用,其須要一個實參:一個函數,用於指定要用來進行比較的函數。花括號中的代碼 { it.age } 就是實現了這個邏輯的 Lambda 表達式

上述 maxBy 函數的實參是簡化後的寫法,這裏來看下 maxBy 函數的簡化過程

最原始的語法聲明應該是這樣的,用括號包裹着 Lambda 表達式

println(people.maxBy({ p: Person -> p.age }))
複製代碼

kotlin 有一種語法約定,若是 Lambda 表達式是函數調用的最後一個實參,能夠將之放到括號的外邊

println(people.maxBy() { p: Person -> p.age })
複製代碼

當 Lamdba 表達式是函數惟一的實參時,能夠去掉調用代碼中的空括號對

println(people.maxBy { p: Person -> p.age })
複製代碼

當 Lambda 表達式的參數類型是能夠被推導出來時就能夠省略聲明參數類型

println(people.maxBy { p -> p.age })
複製代碼

若是當前上下文期待的是隻有一個參數的 Lambda 表達式且參數類型能夠被推斷出來,就會爲該參數生成一個默認名稱:it

println(people.maxBy { it.age })
複製代碼

kotlin 和 Java 的一個顯著區別就是,在 kotlin 中函數內部的 Lambda 表達式不會僅限於訪問函數的參數以及 final 變量,在 Lambda 內部也能夠訪問並修改非 final 變量

從 Lambda 內部訪問外部變量,咱們稱這些變量被 Lambda 捕捉。當捕捉 final 變量時,變量值和使用這個值的 Lambda 代碼一塊兒存儲,對非 final 變量來講,其值被封裝在一個特殊的包裝器中,對這個包裝器的引用會和 Lambda 代碼一塊兒存儲

var number = 0
    val list = listOf(10, 20, 30, 40)
    list.forEach {
        if (it > 20) {
            number++
        }
    }
    println(number) //2
複製代碼

成員引用用於建立一個調用單個方法或者訪問單個屬性的函數值,經過雙冒號把類名稱和要引用的成員(一個方法或者一個屬性)名稱分隔開

成員引用的一個用途就是:若是要當作參數傳遞的代碼塊已經被定義成了函數,此時沒必要專門建立一個調用該函數的 Lambda 表達式,能夠直接經過成員引用的方式來傳遞該函數(也能夠傳遞屬性)。此外,成員引用對擴展函數同樣適用

data class Person(val name: String, val age: Int) {

    val myAge = age

    fun getPersonAge() = age
}

fun Person.filterAge() = age

fun main() {
    val people = listOf(Person("leavesC", 24), Person("Ye", 22))
    println(people.maxBy { it.age })    //Person(name=leavesC, age=24)
    println(people.maxBy(Person::age))  //Person(name=leavesC, age=24)
    println(people.maxBy(Person::myAge))  //Person(name=leavesC, age=24)
    println(people.maxBy(Person::getPersonAge))  //Person(name=leavesC, age=24)
    println(people.maxBy(Person::filterAge))  //Person(name=leavesC, age=24)
}
複製代碼

無論引用的是函數仍是屬性,都不要在成員引用的名稱後面加括號

此外,還能夠引用頂層函數

fun test() {
    println("test")
}

fun main() {
    val t = ::test
}
複製代碼

也能夠用構造方法引用存儲或者延期執行建立類實例的動做

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

fun main() {
    val createPerson = ::Person
    val person = createPerson("leavesC", 24)
    println(person)
}
複製代碼

十7、標準庫中的擴展函數

kotlin 標準庫中提供了幾個比較實用的擴展函數,定義在 Standard 文件下

17.一、run

run 函數接收一個函數參數並以該函數的返回值做爲 run 函數的返回值

@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}
複製代碼

用例

fun main() {
    var nickName = "leavesC"
    nickName = nickName.run {
        if (isNotEmpty()) {
            this
        } else {
            ""
        }
    }
    println(nickName)
}
複製代碼

17.二、with

with 函數並非擴展函數,不過因爲做用相近,此處就一塊兒介紹了。with 函數的第一個參數是接受者對象 receiver,第二個參數是在 receiver 對象類型上定義的擴展函數,因此能夠在函數內部直接調用 receiver 其公開的方法和屬性

@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}
複製代碼

with 函數用於對同一個對象執行屢次操做而不須要反覆把對象的名稱寫出來

例如,爲了構建一個包含指定內容的字符串,須要前後以下調用

fun main() {
    val result = StringBuilder()
    result.append("leavesC")
    result.append("\n")
    for (letter in 'A'..'Z') {
        result.append(letter)
    }
    println(result.toString())
 }
複製代碼

改成經過 with 函數來構建的話會代碼會簡潔許多

val result = with(StringBuilder()) {
        append("leavesC")
        append("\n")
        for (letter in 'A'..'Z') {
            append(letter)
        }
        toString()
    }
    println(result)
複製代碼

with 函數是一個接受兩個參數的函數,在這個例子中就是一個 StringBuilder 和一個 Lambda 表達式,這裏利用了把 Lambda 表達式放在括號外的約定

with 函數的返回值是執行 Lambda 表達式的結果,該結果就是 Lambda 中的最後一個表達式的返回值,所以若是將代碼修改成以下所示的話,由於 println() 方法無返回值,因此打印出來的內容將是 kotlin.Unit

val result = with(StringBuilder()) {
        append("leavesC")
        append("\n")
        for (letter in 'A'..'Z') {
            append(letter)
        }
        println("Hello")
    }
    println(result)  //kotin.Unit
複製代碼

17.三、apply

apply 函數被聲明爲類型 T 的擴展函數,它的接收者是做爲實參的 Lambda 的接受者,最終函數返回 this 即對象自己

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
複製代碼

因此apply 函數和 with 函數的惟一區別在於:apply 函數始終會返回做爲實參傳遞給它的對象

val result = StringBuilder().apply {
        append("leavesC")
        append("\n")
        for (letter in 'A'..'Z') {
            append(letter)
        }
        toString()
    }
    println(result)
    println(result.javaClass) //class java.lang.StringBuilder
複製代碼

17.四、also

also 函數接收一個函數類型的參數,該參數又以接收者自己做爲參數,最終返回接收者對象自己

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}
複製代碼

用例

fun main() {
    val nickName = "leavesC"
    val also = nickName.also {
        it.length
    }
    println(also) //leavesC
}
複製代碼

17.五、let

also 函數接收一個函數類型的參數,該參數又以接收者自己做爲參數,最終返回函數的求值結果

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}
複製代碼

用例

fun main() {
    val nickName = "leavesC"
    val also = nickName.let {
        it.length
    }
    println(also) //7
}
複製代碼

17.六、takeIf

takeIf 接收一個返回值類型爲 bool 的函數,當該參數返回值爲 true 時返回接受者對象自己,不然返回 null

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
}
複製代碼

用例

fun main() {
    println(check("leavesC")) //7
    println(check(null)) //0
}

fun check(name: String?): Int {
    return name.takeIf { !it.isNullOrBlank() }?.length ?: 0
}
複製代碼

17.七、takeUnless

takeUnless 的判斷條件與 takeIf 相反,這裏再也不贅述

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (!predicate(this)) this else null
}
複製代碼

十8、函數操做符

18.一、總數操做符

18.1.一、any

若是至少有一個元素符合給出的判斷條件,則返回 true

val list = listOf(1, 3, 5, 7, 9)
    println(list.any { it > 13 })  //false
    println(list.any { it > 7 })   //true
複製代碼
18.1.二、all

若是所有的元素符合給出的判斷條件,則返回 true

val list = listOf(1, 3, 5, 7, 9)
    println(list.all { it > 13 })  //false
    println(list.all { it > 0 })   //true
複製代碼
18.1.三、count

返回符合給出判斷條件的元素總數

val list = listOf(1, 3, 5, 7, 9)
    println(list.count { it > 7 })  //1
    println(list.count { it > 2 })  //4
複製代碼
18.1.四、fold

在一個初始值的基礎上從第一項到最後一項經過一個函數累計全部的元素

fun main() {
    val list = listOf(1, 3, 5, 7, 9)
    println(list.fold(2) { total, next->
        println("$next , $total")
        next + total
    })
}
複製代碼
1 , 2
3 , 3
5 , 6
7 , 11
9 , 18
27
複製代碼
18.1.五、foldRight

與 fold 同樣,但順序是從最後一項到第一項

val list = listOf(1, 3, 5, 7, 9)
    println(list.foldRight(2) { next, total->
        println("$next , $total")
        next + total
    })
複製代碼
9 , 2
7 , 11
5 , 18
3 , 23
1 , 26
27
複製代碼
18.1.六、forEach
val list = listOf(1, 3, 5, 7, 9)
    list.forEach { print(it + 1) } //246810
複製代碼
18.1.七、forEachIndexed

相似於 forEach ,同時能夠獲得元素的索引

val list = listOf(1, 3, 5, 7, 9)
    list.forEachIndexed { index, value -> println("$index value is $value") }

    0 value is 1
	1 value is 3
	2 value is 5
    3 value is 7
	4 value is 9
複製代碼
18.1.八、max

返回最大的一項,若是沒有則返回null

val list = listOf(1, 3, 5, 7, 9)
    println(list.max()) //9
複製代碼
18.1.九、maxBy

根據給定的函數返回最大的一項,若是沒有則返回 null

val list = listOf(1, 3, 5, 7, 9)
    println(list.maxBy { -it }) //1
複製代碼
18.1.十、min

返回最小的一項,若是沒有則返回null

val list = listOf(1, 3, 5, 7, 9)
    println(list.min()) //1
複製代碼
18.1.十一、minBy

根據給定的函數返回最小的一項,若是沒有則返回null

val list = listOf(1, 3, 5, 7, 9)
    println(list.minBy { -it }) //9
複製代碼
18.1.十二、none

若是沒有任何元素與給定的函數匹配,則返回true

val list = listOf(1, 3, 5, 7, 9)
    println(list.none { it > 10 }) //true
複製代碼
18.1.1三、reduce

與 fold 同樣,可是沒有一個初始值。經過一個函數從第一項到最後一項進行累計

val list = listOf(1, 3, 5, 7, 9)
    println(list.reduce { total, next ->
        println("$next , $total")
        total + next
    })
	3 , 1
	5 , 4
	7 , 9
	9 , 16
	25
複製代碼
18.1.1四、reduceRight

與 reduce 同樣,可是順序是從最後一項到第一項

val list = listOf(1, 3, 5, 7, 9)
    println(list.reduceRight { next, total ->
        println("$next , $total")
        total + next
    })

	7 , 9
	5 , 16
	3 , 21
	1 , 24
	25
複製代碼
18.1.1五、sumBy

返回全部每一項經過函數轉換以後的數據的總和

val list = listOf(1, 3, 5, 7, 9)
    println(list.sumBy { it + 1 }) //30
複製代碼

18.二、過濾操做符

18.2.一、drop

返回包含去掉前n個元素的全部元素的列表

val list = listOf(1, 3, 5, 7, 9)
    println(list.drop(2)) //[5, 7, 9]
複製代碼
18.2.二、dropWhile

返回從第一個開始不符合給定函數的元素起以後的列表

val list = listOf(1, 3, 5, 7, 9, 2)
    println(list.dropWhile { it < 4 }) //[5, 7, 9, 2]
複製代碼
18.2.三、dropLastWhile

從最後一項開始,返回從開始不符合給定函數的元素起以後的列表

val list = listOf(10, 1, 3, 5, 7, 9)
    println(list.dropLastWhile { it > 4 }) //[10, 1, 3]
複製代碼
18.2.四、filter

過濾全部符合給定函數條件的元素

val list = listOf(1, 3, 5, 7, 9, 2)
    println(list.filter { it < 4 }) //[1, 3, 2]
複製代碼
18.2.五、filterNot

過濾全部不符合給定函數條件的元素

val list = listOf(1, 3, 5, 7, 9, 2)
    println(list.filterNot { it < 4 }) //[5, 7, 9]
複製代碼
18.2.六、filterNotNull

過濾全部元素中不是null的元素

val list = listOf(1, 3, 5, 7, 9, 2, null)
    println(list.filterNotNull()) //[1, 3, 5, 7, 9, 2]
複製代碼
18.2.七、slice

過濾一個list中指定index的元素

val list = listOf(1, 3, 5, 7, 9, 2, null)
    println(list.slice(listOf(0, 3))) //[1, 7]
複製代碼
18.2.八、take

返回從第一個開始的n個元素

val list = listOf(1, 3, 5, 7, 9, 2, null)
    println(list.take(2)) //[1, 3]
複製代碼
18.2.九、takeLast

返回從最後一個開始的n個元素

val list = listOf(1, 3, 5, 7, 9, 2, null)
    println(list.takeLast(2)) //[2, null]
複製代碼
18.2.十、takeWhile

返回從第一個開始符合給定函數條件的元素。

val list = listOf(1, 3, 5, -1, 7, 9, 2)
    println(list.takeWhile { it > 2 }) //[]
    println(list.takeWhile { it > 0 }) //[1, 3, 5]
複製代碼

18.三、映射操做符

18.3.一、flatMap

遍歷全部的元素,爲每個建立一個集合,最後把全部的集合放在一個集合中

val list = listOf(1, 3, 5, -1, 7, 9, 2)
    println(list.flatMap { listOf(it, it + 1) }) //[1, 2, 3, 4, 5, 6, -1, 0, 7, 8, 9, 10, 2, 3]
複製代碼
18.3.二、groupBy

返回一個根據給定函數分組後的map

val list = listOf(1, 3, 5, -1, 7, 9, 2)
    println(list.groupBy { listOf(it) }) //{[1]=[1], [3]=[3], [5]=[5], [-1]=[-1], [7]=[7], [9]=[9], [2]=[2]}
    println(list.groupBy { listOf(it, it + 1) }) //{[1, 2]=[1], [3, 4]=[3], [5, 6]=[5], [-1, 0]=[-1], [7, 8]=[7], [9, 10]=[9], [2, 3]=[2]}
複製代碼
18.3.三、map

返回一個每個元素根據給定的函數轉換所組成的List。

val list = listOf(1, 3, 5, -1, 7, 9, 2)
    println(list.map { listOf(it) }) //[[1], [3], [5], [-1], [7], [9], [2]]
    println(list.map { listOf(it, it + 1) }) //[[1, 2], [3, 4], [5, 6], [-1, 0], [7, 8], [9, 10], [2, 3]]
複製代碼
18.3.四、mapIndexed

返回一個每個元素根據給定的包含元素index的函數轉換所組成的List

val list = listOf(1, 3, 5, -1, 7, 9, 2)
    println(list.mapIndexed { index, value -> index }) //[0, 1, 2, 3, 4, 5, 6]
    println(list.mapIndexed { index, value -> index * value }) //[0, 3, 10, -3, 28, 45, 12]
複製代碼
18.3.五、mapNotNull

返回一個每個非null元素根據給定的函數轉換所組成的List

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
    println(list.mapNotNull { it }) //[1, 3, 5, -1, 7, 9, 2]
複製代碼

18.四、元素操做符

18.4.一、contains

若是指定元素能夠在集合中找到,則返回true

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
    println(list.contains(3)) //true
    println(list.contains(13)) //false
複製代碼
18.4.二、elementAt

返回給定index對應的元素,若是index數組越界則會拋出 IndexOutOfBoundsException

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
    println(list.elementAt(3)) //-1
    println(list.elementAt(6)) //null
複製代碼
11.4.三、elementAtOrElse

返回給定index對應的元素,若是index數組越界則會根據給定函數返回默認值

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
    println(list.elementAtOrElse(3, { it * 2 }))  //-1
    println(list.elementAtOrElse(16, { it * 2 })) //32

複製代碼
18.4.四、elementAtOrNull

返回給定index對應的元素,若是index數組越界則會返回null

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
    println(list.elementAtOrNull(3))  //-1
    println(list.elementAtOrNull(16)) //null
複製代碼
18.4.五、first

返回符合給定函數條件的第一個元素

val list = listOf(1, 3, 5, -1, 7, 9, 2)
    println(list.first { it % 3 == 0 })  //3
複製代碼
18.4.六、firstOrNull

返回符合給定函數條件的第一個元素,若是沒有符合則返回null

val list = listOf(1, 3, 5, -1, 7, 9, 2)
    println(list.firstOrNull { it % 3 == 0 })  //3
    println(list.firstOrNull { it % 8 == 0 })  //null
複製代碼
18.4.七、indexOf

返回指定元素的第一個index,若是不存在,則返回 -1

val list = listOf(1, 3, 5, -1, 7, 9, 2)
    println(list.indexOf(5))  //2
    println(list.indexOf(12)) //-1
複製代碼
18.4.八、indexOfFirst

返回第一個符合給定函數條件的元素的index,若是沒有符合則返回 -1

val list = listOf(1, 3, 5, 1, 7, 9, 2)
    println(list.indexOfFirst { it % 2 == 0 })   //6
    println(list.indexOfFirst { it % 12 == 0 })  //-1
複製代碼
18.4.九、indexOfLast

返回最後一個符合給定函數條件的元素的index,若是沒有符合則返回 -1

val list = listOf(1, 3, 5, 6, 7, 9, 2)
    println(list.indexOfLast { it % 2 == 0 })   //6
    println(list.indexOfLast { it % 12 == 0 })  //-1
複製代碼
18.4.十、last

返回符合給定函數條件的最後一個元素

val list = listOf(1, 3, 5, 6, 7, 9, 2)
    println(list.last { it % 2 == 0 })   //2
    println(list.last { it % 3 == 0 })   //9
複製代碼
18.4.十、lastIndexOf

返回指定元素的最後一個index,若是不存在,則返回 -1

val list = listOf(1, 3, 2, 6, 7, 9, 2)
    println(list.lastIndexOf(2))    //6
    println(list.lastIndexOf(12))   //-1
複製代碼
18.4.十一、lastOrNull

返回符合給定函數條件的最後一個元素,若是沒有符合則返回null

val list = listOf(1, 3, 2, 6, 7, 9, 2)
    println(list.lastOrNull { it / 3 == 3 })    //9
    println(list.lastOrNull { it == 10 })       //null
複製代碼
18.4.十二、single

返回符合給定函數的單個元素,若是沒有符合或者超過一個,則拋出異常

val list = listOf(1, 9, 2, 6, 7, 9, 2)
    println(list.single { it % 7 == 0 })  //7
    println(list.single { it == 2 })      //IllegalArgumentException
複製代碼
18.4.1三、singleOrNull

返回符合給定函數的單個元素,若是沒有符合或者超過一個,則返回null

val list = listOf(1, 9, 2, 6, 7, 9, 2)
    println(list.singleOrNull { it % 7 == 0 })  //7
    println(list.singleOrNull { it == 2 })      //null
複製代碼

18.五、生產操做符

18.5.一、partition

把一個給定的集合分割成兩個,第一個集合是由原集合每一項元素匹配給定函數條 件返回 true 的元素組成,第二個集合是由原集合每一項元素匹配給定函數條件返回 false 的元素組成

val list = listOf(1, 9, 2, 6, 7, 9, 2)
    val (list1, list2) = list.partition { it % 2 == 0 }
    println(list1)  //[2, 6, 2]
    println(list2)  //[1, 9, 7, 9]
複製代碼
18.5.二、plus

返回一個包含原集合和給定集合中全部元素的集合,由於函數的名字緣由,咱們可使用 + 操做符

val list1 = listOf(1, 9, 2, 6, 7, 9, 2)
    val list2 = listOf(1, 2, 4, 6, 8, 10)
    println(list1.plus(list2)) //[1, 9, 2, 6, 7, 9, 2, 1, 2, 4, 6, 8, 10]
    println(list1 + list2)  //[1, 9, 2, 6, 7, 9, 2, 1, 2, 4, 6, 8, 10]
複製代碼
18.5.三、zip

返回由 pair 組成的List,每一個 pair 由兩個集合中相同index的元素組成。這個返回的List的大小由最小的那個集合決定

val list1 = listOf(1, 9, 2, 6, 7, 9, 2)
    val list2 = listOf(1, 2, 4, 6, 8, 10)
    val list3 = list1.zip(list2)
    println(list3.javaClass)
    println(list3.get(0).javaClass)
    println("${list3.get(0).first} , ${list3.get(0).second}")
    list3.forEach { println(it) }
複製代碼
class java.util.ArrayList
    class kotlin.Pair
    1 , 1
    (1, 1)
    (9, 2)
    (2, 4)
    (6, 6)
    (7, 8)
    (9, 10)
複製代碼
18.5.四、unzip

從包含pair的List中生成包含List的Pair

val list1 = listOf(Pair("leavesC", 1), Pair("leavesC_2", 2), Pair("leavesC_3", 3))
    val list2 = list1.unzip()
    println(list2.javaClass)
    println(list2.first)
    println(list2.second)
複製代碼
class kotlin.Pair
    [leavesC, leavesC_2, leavesC_3]
    [1, 2, 3]
複製代碼

18.六、順序操做符

18.6.一、reverse

返回一個與指定list相反順序的list

val list1 = listOf(Pair("leavesC", 1), Pair("leavesC_2", 2), Pair("leavesC_3", 3))
    val list2 = list1.reversed()
    println(list2)      //[(leavesC_3, 3), (leavesC_2, 2), (leavesC, 1)]
複製代碼
18.6.二、sort

返回一個天然排序後的list

val list1 = listOf(2, 4, 1, 9, 5, 10)
    val list2 = list1.sorted()
    println(list2) //[1, 2, 4, 5, 9, 10]

    val list3 = listOf("a", "c", "ab", "b", "cdd", "cda")
    val list4 = list3.sorted()
    println(list4) //[a, ab, b, c, cda, cdd]
複製代碼
18.6.三、sortBy

返回一個根據指定函數排序後的list

val list1 = listOf(2, 4, 1, 9, 5, 10)
    val list2 = list1.sortedBy { it - 3 }
    println(list2) //[1, 2, 4, 5, 9, 10]
複製代碼
18.6.四、sortDescending

返回一個降序排序後的List

val list1 = listOf(2, 4, 1, 9, 5, 10)
    val list2 = list1.sortedDescending()
    println(list2) //[10, 9, 5, 4, 2, 1]
複製代碼
18.6.五、sortDescendingBy

返回一個根據指定函數降序排序後的list

val list1 = listOf(2, 4, 1, 9, 5, 10)
    val list2 = list1.sortedByDescending { it % 2 }
    println(list2) //[1, 9, 5, 2, 4, 10]
複製代碼

十9、異常

kotlin 中異常處理的基本形式和 Java 相似

fun compute(index: Int): Boolean {
    if (index !in 0..10) {
        throw IllegalArgumentException("參數錯誤")
    }
    return true
}
複製代碼

和 Java 不一樣的是,kotlin 中 throw 結構是一個表達式,能夠做爲另外一個表達式的一部分來使用

例以下面這個例子,若是條件不知足,則將拋出異常,從而致使 status 變量也不會初始化

val status = if (index in 0..10) index else throw IllegalArgumentException("參數錯誤")
複製代碼

此外,在 Java 中對於受檢異常必須顯式地處理,經過 try/catch 語句捕獲異常或者是拋給其調用者來處理。而 kotlin 不區分受檢異常和未受檢異常,不用指定函數拋出的異常,能夠處理也能夠不處理異常

在 kotlin 中 ,try 關鍵字引入了一個表達式,從而能夠把表達式的值賦給一個變量。若是一個 try 代碼塊執行正常,代碼塊中最後一個表達式就是結果,若是捕獲到了一個異常,則相應 catch 代碼塊中最後一個表達式就是結果

看如下例子,若是 try 表達式包裹的表達式會拋出異常,則返回值爲 null ,不然爲 true

fun main() {
    compute(5)   //fun end : true
    compute(100) //fun end : null
}

fun compute(index: Int) {
    val status = try {
        if (index in 0..10) true else throw IllegalArgumentException("參數錯誤")
    } catch (e: Exception) {
        null
    }
    println("fun end : " + status)
}
複製代碼

可是,若是在 catch 語句中使用 return 結束了 compute 函數,則沒有任何輸出

fun main() {
    compute(5)   //fun end : true
    compute(100) //沒有任何輸出
}

fun compute(index: Int) {
    val status = try {
        if (index in 0..10) true else throw IllegalArgumentException("參數錯誤")
    } catch (e: Exception) {
        return
    }
    println("fun end : " + status)
}
複製代碼

二10、運算符重載

kotlin 容許爲類型提供預約義的操做符實現,這些操做符具備固定的符號表示(例如 + 和 * )和固定的優先級,經過操做符重載能夠將操做符的行爲映射到指定的方法。爲實現這樣的操做符,須要爲類提供一個固定名字的成員函數或擴展函數,相應的重載操做符的函數須要用 operator 修飾符標記

20.一、一元操做符

操做符 函數
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
a++ a.inc()
a-- a.dec()

20.二、二元操做符

操做符 函數
a + b a.plus(b)
a - b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b)
a..b a.rangeTo(b)
a in b b.contains(a)
a !in b !b.contains(a)
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign(b)

20.三、數組操做符

操做符 函數
a[i] a.get(i)
a[i, j] a.get(i, j)
a[i_1, ..., i_n] a.get(i_1, ..., i_n)
a[i] = b a.set(i, b)
a[i, j] = b a.set(i, j, b)
a[i_1, ..., i_n] = b a.set(i_1, ..., i_n, b)

20.四、等於操做符

操做符 函數
a == b a?.equals(b) ?: b === null
a != b !(a?.equals(b) ?: b === null)

相等操做符有一點不一樣,爲了達到正確合適的相等檢查作了更復雜的轉換,由於要獲得一個確切的函數結構比較,不只僅是指定的名稱

方法必需要以下準確地被實現:

operator fun equals(other: Any?): Boolean
複製代碼

操做符 === 和 !== 用來作身份檢查(它們分別是 Java 中的 == 和 != ),而且它們不能被重載

20.五、比較操做符

操做符 函數
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

全部的比較都轉換爲對 compareTo 的調用,這個函數須要返回 Int 值

20.六、函數調用

方法 調用
a() a.invoke()
a(i) a.invoke(i)
a(i, j) a.invoke(i, j)
a(i_1, ..., i_n) a.invoke(i_1, ..., i_n)

20.七、例子

看幾個例子

data class Point(val x: Int, val y: Int) {

    //+Point
    operator fun unaryPlus() = Point(+x, +y)

    //Point++ / ++Point
    operator fun inc() = Point(x + 1, y + 1)

    //Point + Point
    operator fun plus(point: Point) = Point(x + point.x, y + point.y)

    //Point + Int
    operator fun plus(value: Int) = Point(x + value, y + value)

    //Point[index]
    operator fun get(index: Int): Int {
        return when (index) {
            0 -> x
            1 -> y
            else -> throw IndexOutOfBoundsException("無效索引")
        }
    }

    //Point(index)
    operator fun invoke(index: Int) = when (index) {
        0 -> x
        1 -> y
        else -> throw IndexOutOfBoundsException("無效索引")
    }

}
複製代碼
fun main() {
    //+Point(x=10, y=-20) = Point(x=10, y=-20)
    println("+${Point(10, -20)} = ${+Point(10, -20)}")

    //Point(x=10, y=-20)++ = Point(x=10, y=-20)
    var point = Point(10, -20)
    println("${Point(10, -20)}++ = ${point++}")

    //++Point(x=10, y=-20) = Point(x=11, y=-19)
    point = Point(10, -20)
    println("++${Point(10, -20)} = ${++point}")

    //Point(x=10, y=-20) + Point(x=10, y=-20) = Point(x=20, y=-40)
    println("${Point(10, -20)} + ${Point(10, -20)} = ${Point(10, -20) + Point(10, -20)}")

    //Point(x=10, y=-20) + 5 = Point(x=15, y=-15)
    println("${Point(10, -20)} + ${5} = ${Point(10, -20) + 5}")

    point = Point(10, -20)
    //point[0] value is: 10
    println("point[0] value is: ${point[0]}")
    //point[1] value is: -20
    println("point[1] value is: ${point[1]}")

    //point(0) values is: 10
    println("point(0) values is: ${point(0)}")
}
複製代碼

二11、中綴調用與解構聲明

21.一、中綴調用

能夠以如下形式建立一個 Map 變量

fun main() {
    val maps = mapOf(1 to "leavesC", 2 to "ye", 3 to "czy")
    maps.forEach { key, value -> println("key is : $key , value is : $value") }
}
複製代碼

使用 「to」 來聲明 map 的 key 與 value 之間的對應關係,這種形式的函數調用被稱爲中綴調用

kotlin 標準庫中對 to 函數的聲明以下所示,其做爲擴展函數存在,且是一個泛型函數,返回值 Pair 最終再經過解構聲明分別將 key 和 value 傳給 Map

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
複製代碼

中綴調用只能與只有一個參數的函數一塊兒使用,不管是普通的函數仍是擴展函數。中綴符號須要經過 infix 修飾符來進行標記

fun main() {
    val pair = 10 test "leavesC"
    val pair2 = 1.2 test 20
    println(pair2.javaClass) //class kotlin.Pair
}

infix fun Any.test(other: Any) = Pair(this, other)
複製代碼

對於 mapOf 函數來講,它能夠接收不定數量的 Pair 類型對象,所以咱們也能夠經過自定義的中綴調用符 test 來建立一個 map 變量

public fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V> =
    if (pairs.size > 0) pairs.toMap(LinkedHashMap(mapCapacity(pairs.size))) else emptyMap()
複製代碼
val map = mapOf(10 test "leavesC", 20 test "hello")
複製代碼

21.二、解構聲明

有時會有把一個對象拆解成多個變量的需求,在 kotlin 中這種語法稱爲解構聲明

例如,如下例子將 Person 變量結構爲了兩個新變量:name 和 age,而且能夠獨立使用它們

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

fun main() {
    val (name, age) = Person("leavesC", 24)
    println("Name: $name , age: $age")
    //Name: leavesC , age: 24
}
複製代碼

一個解構聲明會被編譯成如下代碼:

val name = person.component1()
    val age = person.component2()
複製代碼

其中的 component1()component2() 函數是在 kotlin 中普遍使用的約定原則的另外一個例子。任何表達式均可以出如今解構聲明的右側,只要能夠對它調用所需數量的 component 函數便可

須要注意的是,componentN() 函數須要用 operator 關鍵字標記,以容許在解構聲明中使用它們

對於數據類來講,其自動生成了 componentN() 函數,而對非數據類,爲了使用解構聲明,須要咱們本身來手動聲明函數

class Point(val x: Int, val y: Int) {
    operator fun component1() = x
    operator fun component2() = y
}

fun main() {
    val point = Point(100, 200)
    val (x, y) = point
    println("x: $x , y: $y")
    //x: 100 , y: 200
}
複製代碼

若是咱們須要從一個函數返回兩個或者更多的值,這時候使用解構聲明就會比較方便了

這裏使用的是標準類 Pair 來包裝要傳遞的數據,固然,也能夠自定義數據類

fun computer(): Pair<String, Int> {
    //各類計算
    return Pair("leavesC", 24)
}

fun main() {
    val (name, age) = computer()
    println("Name: $name , age: $age")
}
複製代碼

此外,解構聲明也能夠用在 for 循環中

val list = listOf(Person("leavesC", 24), Person("leavesC", 25))
    for ((name, age) in list) {
        println("Name: $name , age: $age")
    }
複製代碼

對於遍歷 map 一樣適用

val map = mapOf("leavesC" to 24, "ye" to 25)
    for ((name, age) in map) {
        println("Name: $name , age: $age")
    }
複製代碼

一樣也適用於 lambda 表達式

val map = mapOf("leavesC" to 24, "ye" to 25)
    map.mapKeys { (key, value) -> println("key : $key , value : $value") }
複製代碼

若是在解構聲明中不須要某個變量,那麼能夠用下劃線取代其名稱,此時不會調用相應的 componentN() 操做符函數

val map = mapOf("leavesC" to 24, "ye" to 25)
    for ((_, age) in map) {
        println("age: $age")
    }
複製代碼

二12、Object 關鍵字

22.一、對象聲明

在 kotlin 的世界中,能夠經過對象聲明這一功能來實現 Java 中的單例模式,將類聲明與該類的單一實例聲明結合到一塊兒。與類同樣,一個對象聲明能夠包含屬性、方法、初始化語句塊等的聲明,且能夠繼承類和實現接口,惟一不被容許的是構造方法

與普通類的實例不一樣,對象聲明在定義的時候就被當即建立了,不須要在代碼的其它地方調用構造方法,所以爲對象聲明定義構造方法是沒有意義的

interface Fly {

    fun fly()

}

open class Eat {

    fun eat() {
        println("eat")
    }

}

object Animal : Eat(), Fly {

    override fun fly() {
        println("fly")
    }

}

fun main() {
    Animal.fly()
    Animal.eat()
}
複製代碼

kotlin 中的對象聲明被編譯成了經過靜態字段來持有它的單一實例的類,這個字段名字始終都是 INSTANCE

例如,對於 kotlin 中的以下兩個對象聲明

class Test {

    object SingleClass {
        val names = arrayListOf<String>()
    }

    object SingleClass2 {
        val names = arrayListOf<String>()
    }

}
複製代碼

在 Java 代碼中來訪問這兩個對象

public static void main(String[] args) {
        Test.SingleClass.INSTANCE.getNames();
        Test.SingleClass2.INSTANCE.getNames();
    }
複製代碼

22.二、伴生對象

若是須要一個能夠在沒有類實例的狀況下調用可是須要訪問類內部的函數(相似於 Java 中的靜態變量/靜態函數),能夠將其寫成那個類中的對象聲明的成員

經過關鍵字 companion ,就能夠得到經過容器類名稱來訪問這個對象的方法和屬性的能力,再也不須要顯式地指明對象的名稱

class Test {

    companion object {

        const val NAME = ""

        fun testFun() {

        }
    }

}

fun main() {
    Test.NAME
    Test.testFun()
}
複製代碼
22.2.一、工廠模式

能夠利用伴生對象來實現工廠模式

private class User private constructor(val name: String) {

    companion object {
        fun newById(id: Int) = User(id.toString())

        fun newByDouble(double: Double) = User(double.toString())
    }

}

fun main() {
    //構造函數私有,沒法建立
    //val user1 = User("leavesC")
    val user2 = User.newById(10)
    val user3 = User.newByDouble(1.3)
}
複製代碼
22.2.二、指定名稱

伴生對象既能夠爲其指定名字,也能夠直接使用其默認名 Companion,在引用伴生對象時,能夠自由選擇是否要在類名後加上伴生對象名

若是使用的是其默認名 Companion(沒有自定義名稱),則如下兩種引用方式都是等價的

val user2 = User.Companion.newById(10)
    val user3 = User.newByDouble(1.3)
複製代碼

若是爲伴生對象聲明瞭自定義名稱,引用方式等同

private class User private constructor(val name: String) {

    companion object UserLoader {
        fun newById(id: Int) = User(id.toString())

        fun newByDouble(double: Double) = User(double.toString())
    }

}

fun main() {
    //構造函數私有,沒法建立
    //val user1 = User("leavesC")
    val user2 = User.UserLoader.newById(10)
    val user3 = User.newByDouble(1.3)
}
複製代碼
22.2.三、實現接口

伴生對象也能夠實現接口,且能夠直接將包含它的類的名字當作實現了該接口的對象實例來使用

private class User private constructor(val name: String) {

    companion object UserLoader : Runnable {

        override fun run() {

        }
    }

}

fun newThread(runnable: Runnable) = Thread(runnable)

fun main() {
    //User 會直接被當作 Runnable 的實例
    val thread = newThread(User)
    val thread2 = newThread(User.UserLoader)
}
複製代碼

22.三、對象表達式

object 能用來聲明匿名對象,可用於替代 Java 中的匿名內部類,且對象表達式中的代碼能夠訪問並修改其外部的非 final 型的變量

fun newThread(runnable: Runnable) = Thread(runnable)

fun main() {
    var count = 0
    val thread = newThread(object : Runnable {
        override fun run() {
            count++
        }
    })
}
複製代碼

二十3、委託

23.一、委託模式

委託模式是一種基本的設計模式,該模式下有兩個對象參與處理同一個請求,接受請求的對象將請求委託給另外一個對象來處理。kotlin 原生支持委託模式,能夠零樣板代碼來實現,經過關鍵字 by 實現委託

interface Printer {

    fun print()
    
}

class DefaultPrinter : Printer {

    override fun print() {
         println("DefaultPrinter print")
    }

}

class CustomPrinter(val printer: Printer) : Printer by printer

fun main() {
    val printer = CustomPrinter(DefaultPrinter())
    printer.print() //DefaultPrinter print
}
複製代碼

CustomPrinter 的 by 子句表示將會在 CustomPrinter 中存儲 printer 變量,而且編譯器將爲 CustomPrinter 隱式生成 Printer 接口的全部抽象方法,並將這些方法的調用操做轉發給 printer

此外,CustomPrinter 也能夠決定本身實現部分方法或所有本身實現,但重寫的成員不會在委託對象的成員中調用 ,委託對象的成員只能訪問其自身對接口成員實現

interface Printer {

    val message: String

    fun print()

    fun reprint()

}

class DefaultPrinter : Printer {

    override val message: String = "DefaultPrinter message"

    override fun print() {
        println(message)
    }

    override fun reprint() {
        println("DefaultPrinter reprint")
    }

}

class CustomPrinter(val printer: Printer) : Printer by printer {

    override val message: String = "CustomPrinter message"

    override fun reprint() {
        println("CustomPrinter reprint")
    }

}

fun main() {
    val printer = CustomPrinter(DefaultPrinter())
    printer.print() //DefaultPrinter message
    printer.reprint() //CustomPrinter reprint
}
複製代碼

23.二、屬性委託

kotlin 支持經過委託屬性將對一個屬性的訪問操做委託給另一個對象來完成,對應的語法格式是:

val/var <屬性名>: <類型> by <表達式>
複製代碼

屬性的委託沒必要實現任何的接口,但須要提供一個 getValue() 方法與 setValue()(對於 var 屬性),對一個屬性的 get 和 set 操做會被委託給屬性的委託的這兩個方法

class Delegate {
    //第一個參數表示被委託的對象、第二個參數表示被委託對象自身的描述
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
    }
	//第一個參數表示被委託的對象、第二個參數表示被委託對象自身的描述,第三個參數是將要賦予的值
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    }
}
複製代碼

看如下的小例子,經過輸出值就能夠看出各個方法的調用時機

package test

import kotlin.reflect.KProperty

class Delegate {

    private var message: String? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("${thisRef?.javaClass?.name}, thank you for delegating '${property.name}' to me!")
        return message ?: "null value"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in ${thisRef?.javaClass?.name}.")
        message = value
    }
}

class Example {
    var strValue: String by Delegate()
}

fun main() {
    val example = Example()
    println(example.strValue)
    example.strValue = "leaveC"
    println(example.strValue)
// test.Example, thank you for delegating 'strValue' to me!
// null value
// leaveC has been assigned to 'strValue' in test.Example.
// test.Example, thank you for delegating 'strValue' to me!
// leaveC
}
複製代碼

23.三、延遲屬性

lazy() 是接受一個 lambda 並返回一個 Lazy < T > 實例的函數,返回的實例能夠做爲實現延遲屬性的委託,第一次調用 get() 會執行已傳遞給 lazy() 函數的 lambda 表達式並記錄結果, 後續調用 get() 只是返回記錄的結果

class Example {

    val lazyValue1: String by lazy {
        println("lazyValue1 computed!")
        "Hello"
    }

    val lazyValue2: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
        println("lazyValue2 computed!")
        computeLazyValue()
    }

    private fun computeLazyValue() = "leavesC"

}

fun main() {
    val example = Example()
    println(example.lazyValue1) //lazyValue1 computed! Hello
    println(example.lazyValue1) //Hello
    println(example.lazyValue2) //lazyValue2 computed! leavesC
}
複製代碼

默認狀況下,對於 lazy 屬性的求值是帶同步鎖的(synchronized),即帶有 LazyThreadSafetyMode.SYNCHRONIZED 參數,此時該值只容許同一時刻只能有一個線程對其進行初始化,而且全部線程會看到相同的初始化值。若是初始化委託的同步鎖不是必需的,即若是容許多個線程同時執行,那麼能夠將 LazyThreadSafetyMode.PUBLICATION 做爲參數傳遞給 lazy() 函數。 而若是你肯定初始化將老是發生在單個線程,那麼可使用 LazyThreadSafetyMode.NONE 模式, 此時不會有任何線程安全的保證以及相關的資源開銷

23.四、可觀察屬性

Delegates.observable() 接受兩個參數:初始值以及修改屬性值時的回調函數。當爲屬性賦值後就會調用該回調函數,該回調函數包含三個參數:被賦值的屬性、舊值與新值

fun main() {
    val example = Example()
    example.age = 24 //kProperty.name: age , oldValue: -100 , newValue: 24
    example.age = 27 //kProperty.name: age , oldValue: 24 , newValue: 27
}

class Example {
    var age: Int by Delegates.observable(-100) { kProperty: KProperty<*>, oldValue: Int, newValue: Int ->
        println("kProperty.name: ${kProperty.name} , oldValue: $oldValue , newValue: $newValue")
    }
}
複製代碼

若是想要攔截一個賦值操做並判斷是否進行否決,可使用 vetoable() 函數,經過返回一個布爾值來決定是否進行攔截,該判斷邏輯是在屬性被賦新值生效以前進行

fun main() {
    val example = Example()
    example.age = 24  //kProperty.name: age , oldValue: -100 , newValue: 24
    example.age = -10 //kProperty.name: age , oldValue: 24 , newValue: -10
    example.age = 30  //kProperty.name: age , oldValue: 24 , newValue: 30 (oldValue 依然是 24,說明第二次的賦值操做被否決了)
}

class Example {
    var age: Int by Delegates.vetoable(-100) { kProperty: KProperty<*>, oldValue: Int, newValue: Int ->
        println("kProperty.name: ${kProperty.name} , oldValue: $oldValue , newValue: $newValue")
        age <= 0 //返回true 則表示攔截該賦值操做
    }
}
複製代碼

23.五、把屬性儲存在映射中

能夠在一個 map 映射裏存儲屬性的值,而後把屬性的存取操做委託給 map 進行管理

fun main() {
    val student = Student(
        mapOf(
            "name" to "leavesC",
            "age" to 24
        )
    )
    println(student.name)
    println(student.age)
}

class Student(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
}
複製代碼

在上述示例中,屬性 name 和 age 都是不可變的(val),所以 map 的類型也是 Map 而非 MutableMap(MutableMap 在賦值後能夠修改),所以若是爲了支持 var 屬性,能夠將只讀的 Map 換成 MutableMap

23.六、局部委託屬性

能夠將局部變量聲明爲委託屬性

class Printer {

    fun print() {
        println("temp.Printer print")
    }

}

fun getPrinter(): Printer {
    println("temp.Printer getPrinter")
    return Printer()
}

//局部委託
fun example(getPrinter: () -> Printer) {
    val lPrinter by lazy(getPrinter)
    val valid = true
    if (valid) {
        lPrinter.print()
    }
}

fun main() {
    example { getPrinter() }
    //temp.Printer getPrinter
    //temp.Printer print
}
複製代碼

委託變量只會在第一次訪問時纔會進行初始化,所以若是 valid 爲 false 的話,getPrinter() 方法就不會被調用

二十4、註解

註解是將元數據附加到代碼元素上的一種方式,附件的元數據就能夠在編譯後的類文件或者運行時被相關的源代碼工具訪問

註解的語法格式以下所示:

annotation class AnnotationName()
複製代碼

註解的附加屬性能夠經過用元註解標註註解類來指定:

  • @Target 指定該註解標註的容許範圍(類、函數、屬性等)
  • @Retention 指定該註解是否要存儲在編譯後的 class 文件中,若是要保存,則在運行時能夠經過反射來獲取到該註解值
  • @Repeatable 標明容許在單個元素上屢次使用相同的該註解
  • @MustBeDocumented 指定該註解是公有 API 的一部分,而且應該包含在生成的 API 文檔中顯示的類或方法的簽名中
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
    @Retention(AnnotationRetention.RUNTIME)
    @Repeatable
    @MustBeDocumented
    annotation class AnnotationName()
複製代碼

註解能夠聲明包含有參數的構造函數

annotation class OnClick(val viewId: Long)
複製代碼

容許的參數類型有:

  • 原生數據類型,對應 Java 原生的 int 、long、char 等
  • 字符串
  • class 對象
  • 枚舉
  • 其餘註解
  • 以上類型的數組

註解參數不能包含有可空類型,由於 JVM 不支持將 null 做爲註解屬性的值來存儲

看一個在運行時獲取註解值的例子

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class OnClick(val viewId: Long)

class AnnotationsTest {

    @OnClick(200300)
    fun onClickButton() {
        println("Clicked")
    }

}

fun main() {
    val annotationsTest = AnnotationsTest()
    for (method in annotationsTest.javaClass.methods) {
        for (annotation in method.annotations) {
            if (annotation is OnClick) {
                println("method name: " + method.name)  //method name: onClickButton
                println("OnClick viewId: " + annotation.viewId)  //OnClick viewId: 200300
            }
        }
    }
}
複製代碼
相關文章
相關標籤/搜索