Kotlin基礎(chǔ) -- 2

五框弛、Lambda編程

1.Lambda表達(dá)式和成員引用

Lambda簡(jiǎn)介:作為函數(shù)參數(shù)的代碼塊。可以理解為簡(jiǎn)化表達(dá)后的匿名函數(shù)冶共,實(shí)質(zhì)上它就是一種語法糖。

用匿名內(nèi)部類實(shí)現(xiàn)監(jiān)聽器:

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.d("TAG", "zwm, test java")
    }
});

用lambda實(shí)現(xiàn)監(jiān)聽器:

button.setOnClickListener{ Log.d(TAG, "zwm, test lambda") }

Lambda和集合

手動(dòng)在集合中搜索:

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

fun method() {
    val people = listOf(Person("Android", 10), Person("Java", 20), Person("Kotlin", 5))
    var maxAge = 0
    var theOldest: Person? = null
    for (person in people) {
        if (person.age > maxAge) {
            maxAge = person.age
            theOldest = person
        }
    }
    Log.d("TAG", "zwm, theOldest: ${theOldest?.name} ${theOldest?.age}")
}

日志打用拷纭:
2020-08-19 15:39:44.995 15807-15807/? D/TAG: zwm, theOldest: Java 20

用lambda在集合中搜索:

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

fun method() {
    val people = listOf(Person("Android", 10), Person("Java", 20), Person("Kotlin", 5))
    var theOldest = people.maxBy { it.age }
    Log.d("TAG", "zwm, theOldest: ${theOldest?.name} ${theOldest?.age}")
}

日志打油苯:
2020-08-19 15:41:39.258 16117-16117/com.tomorrow.kotlindemo D/TAG: zwm, theOldest: Java 20

maxBy函數(shù)可以在任何集合上調(diào)用,且只需要一個(gè)實(shí)參:一個(gè)函數(shù)眨层,指定比較哪個(gè)值來找到最大元素庙楚。

如果lambda剛好是函數(shù)或者屬性的委托,可以用成員引用替換:

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

fun method() {
    val people = listOf(Person("Android", 10), Person("Java", 20), Person("Kotlin", 5))
    var theOldest = people.maxBy(Person::age )
    Log.d("TAG", "zwm, theOldest: ${theOldest?.name} ${theOldest?.age}")
}

日志打优坑!:
2020-08-19 15:47:17.127 16493-16493/com.tomorrow.kotlindemo D/TAG: zwm, theOldest: Java 20

Lambda表達(dá)式的語法

{ x: Int, y: Int -> x + y } //花括號(hào)馒闷,參數(shù) -> 函數(shù)體

可以把lambda表達(dá)式存儲(chǔ)在一個(gè)變量中,把這個(gè)變量當(dāng)做普通函數(shù)對(duì)待(即通過相應(yīng)實(shí)參調(diào)用它):

fun method() {
    val sum = { x: Int, y: Int -> x + y }
    Log.d("TAG", "zwm, sum: ${sum(1, 2)}")
}

日志打尤鳌:
2020-08-19 15:55:09.737 18193-18193/? D/TAG: zwm, sum: 3

還可以直接調(diào)用lambda表達(dá)式:

fun method() {
    Log.d("TAG", "zwm, sum: ${{ x: Int, y: Int -> x + y }(1, 2)}")
}

日志打幽烧恕:
2020-08-19 15:55:51.879 18556-18556/com.tomorrow.kotlindemo D/TAG: zwm, sum: 3

可以使用庫(kù)函數(shù)run來執(zhí)行傳遞給它的lambda:

fun method() {
    Log.d("TAG", "zwm, sum: ${run{{ x: Int, y: Int -> x + y }(1, 2)}}")
}

日志打印:
2020-08-19 16:01:36.906 19393-19393/com.tomorrow.kotlindemo D/TAG: zwm, sum: 3

Lambda表達(dá)式的簡(jiǎn)明語法推導(dǎo)

最初版本:

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

fun method() {
    val people = listOf(Person("Android", 10), Person("Java", 20), Person("Kotlin", 5))
    var theOldest = people.maxBy({ p: Person -> p.age })
    Log.d("TAG", "zwm, theOldest: ${theOldest?.name} ${theOldest?.age}")
}

Kotlin有這樣一種語法約定:如果lambda表達(dá)式是函數(shù)調(diào)用的最后一個(gè)實(shí)參捺疼,它可以放到括號(hào)的外邊:

fun method() {
    val people = listOf(Person("Android", 10), Person("Java", 20), Person("Kotlin", 5))
    var theOldest = people.maxBy(){ p: Person -> p.age }
    Log.d("TAG", "zwm, theOldest: ${theOldest?.name} ${theOldest?.age}")
}

當(dāng)lambda是函數(shù)唯一的實(shí)參時(shí)疏虫,還可以去掉調(diào)用代碼中的空括號(hào)對(duì):

fun method() {
    val people = listOf(Person("Android", 10), Person("Java", 20), Person("Kotlin", 5))
    var theOldest = people.maxBy{ p: Person -> p.age }
    Log.d("TAG", "zwm, theOldest: ${theOldest?.name} ${theOldest?.age}")
}

和局部變量一樣,如果lambda參數(shù)的類型可以被推導(dǎo)出來,就不需要顯式地指定它:

fun method() {
    val people = listOf(Person("Android", 10), Person("Java", 20), Person("Kotlin", 5))
    var theOldest = people.maxBy{ p -> p.age }
    Log.d("TAG", "zwm, theOldest: ${theOldest?.name} ${theOldest?.age}")
}

如果當(dāng)前上下文期望的是只有一個(gè)參數(shù)的lambda且這個(gè)參數(shù)的類型可以推斷出來,可以使用默認(rèn)參數(shù)名稱it代替命名參數(shù):(僅在實(shí)參名稱沒有顯式地指定時(shí)這個(gè)默認(rèn)的名稱才會(huì)生成)

fun method() {
    val people = listOf(Person("Android", 10), Person("Java", 20), Person("Kotlin", 5))
    var theOldest = people.maxBy{ it.age }
    Log.d("TAG", "zwm, theOldest: ${theOldest?.name} ${theOldest?.age}")
}

如果用變量存儲(chǔ)lambda葛超,那么就沒有可以推斷出參數(shù)類型的上下文,必須顯式地指定參數(shù)類型:

fun method() {
    val people = listOf(Person("Android", 10), Person("Java", 20), Person("Kotlin", 5))
    val getAge = { p: Person -> p.age }
    var theOldest = people.maxBy(getAge)
    Log.d("TAG", "zwm, theOldest: ${theOldest?.name} ${theOldest?.age}")
}

如果Lambda表達(dá)式返回的不是Unit翅敌,那么默認(rèn)最后一行表達(dá)式的值類型就是返回值類型。

在作用域中訪問變量

當(dāng)在函數(shù)內(nèi)聲明一個(gè)匿名內(nèi)部類的時(shí)候醇锚,能夠在這個(gè)匿名內(nèi)部類內(nèi)部引用這個(gè)函數(shù)的參數(shù)和局部變量哼御。也可以用lambda做同樣的事情坯临。如果在函數(shù)內(nèi)部使用lambda,也可以訪問這個(gè)函數(shù)的參數(shù)恋昼,還有在lambda之前定義的局部變量看靠。

在lambda中使用函數(shù)參數(shù):

fun method(name: String, age: Int) {
    { Log.d("TAG", "zwm, name: $name, age: $age") }()
}

日志打印:
2020-08-19 16:48:51.001 26858-26858/com.tomorrow.kotlindemo D/TAG: zwm, name: hello, age: 9

在lambda中改變局部變量:

fun method(name: String, age: Int) {
    var address = "bj"
    {
        address = "gz"
        Log.d("TAG", "zwm, name: $name, age: $age, address: $address")
    }()
    Log.d("TAG", "zwm, address: $address")
}

日志打右杭 :
2020-08-19 17:06:33.875 28625-28625/? D/TAG: zwm, name: hello, age: 9, address: gz
2020-08-19 17:06:33.876 28625-28625/? D/TAG: zwm, address: gz

和Java不一樣挟炬,Kotlin允許在lambda內(nèi)部訪問非final變量甚至修改它們。從lambda內(nèi)訪問外部變量嗦哆,稱這些變量被lambda捕捉谤祖。默認(rèn)情況下,局部變量的生命期被限制在聲明這個(gè)變量的函數(shù)中老速。但是如果它被lambda捕捉了粥喜,使用這個(gè)變量的代碼可以被存儲(chǔ)并稍后再執(zhí)行。

成員引用語法:

類::成員 //雙冒號(hào)把類名稱與要引用的成員(一個(gè)方法或者一個(gè)屬性)名稱隔開

引用屬性:

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

fun method() {
    val people = listOf(Person("Android", 10), Person("Java", 20), Person("Kotlin", 5))
    var theOldest = people.maxBy(Person::age )
    Log.d("TAG", "zwm, theOldest: ${theOldest?.name} ${theOldest?.age}")
}

日志打娱偃:
2020-08-19 17:14:23.246 28854-28854/com.tomorrow.kotlindemo D/TAG: zwm, theOldest: Java 20

引用頂層函數(shù):

fun topMethod(language: String) {
    Log.d("TAG", "zwm, this is top method: $language")
}

fun method() {
    (::topMethod)("Kotlin")
}

2020-08-19 17:40:17.769 9798-9798/com.tomorrow.kotlindemo D/TAG: zwm, this is top method: Kotlin

如果lambda要委托給一個(gè)接收多個(gè)參數(shù)的函數(shù)额湘,提供成員引用代替它將會(huì)非常方便:

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

fun sendEmail(person: Person, content: String) {
    Log.d("TAG", "zwm, sendEmail")
}

fun method() {
    val action = { person: Person, content: String -> sendEmail(person, content) } //這個(gè)lambda委托給sendEmail函數(shù)
    val nextAction = ::sendEmail //可以用成員引用代替
}

可以用構(gòu)造方法引用存儲(chǔ)或者延期執(zhí)行創(chuàng)建類實(shí)例的動(dòng)作。構(gòu)造方法引用的形式是在雙冒號(hào)后指定類名稱:

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

fun method() {
    val createPerson = ::Person
    val p = createPerson("Kotlin", 5)
}

還可以用同樣的方式引用擴(kuò)展函數(shù):

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

fun Person.isAdult() = age >= 21

fun method() {
    val adult = Person::isAdult //盡管isAdult是擴(kuò)展函數(shù)不是Person類的成員旁舰,還是可以通過引用訪問它
    val person = Person("Android", 10)
    Log.d("TAG", "zwm, ${adult(person)}")
    Log.d("TAG", "zwm, ${person.isAdult()}")
}

日志打臃婊:
2020-08-19 18:05:07.156 14079-14079/com.tomorrow.kotlindemo D/TAG: zwm, false
2020-08-19 18:05:07.156 14079-14079/com.tomorrow.kotlindemo D/TAG: zwm, false

綁定引用:

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

fun method() {
    val p = Person("Android", 10)
    val ageFunction = Person::age //使用類引用成員
    Log.d("TAG", "zwm, ageFunction: ${ageFunction(p)}")

    val ageFunction2 = p::age //使用對(duì)象引用成員
    Log.d("TAG", "zwm, ageFunction2: ${ageFunction2()}")
}

日志打印:
2020-08-19 19:43:57.800 20766-20766/com.tomorrow.kotlindemo D/TAG: zwm, ageFunction: 10
2020-08-19 19:43:57.801 20766-20766/com.tomorrow.kotlindemo D/TAG: zwm, ageFunction2: 10

2.集合的函數(shù)式API

基礎(chǔ):filter和map

filter函數(shù)遍歷集合并選出應(yīng)用給定lambda后會(huì)返回true的那些元素箭窜,結(jié)果是一個(gè)新的集合:

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

fun method() {
    val list = listOf(Person("Kotlin", 5), Person("Java", 20), Person("Android", 10))
    val result = list.filter { it.age >= 10 }
    for((index, item) in result.withIndex())
    Log.d("TAG", "zwm, index: $index, name: ${item.name}")
}

日志打犹夯馈:
2020-08-24 00:51:35.432 20309-20309/com.tomorrow.kotlindemo D/TAG: zwm, index: 0, name: Java
2020-08-24 00:51:35.432 20309-20309/com.tomorrow.kotlindemo D/TAG: zwm, index: 1, name: Android

map函數(shù)對(duì)集合中的每一個(gè)元素應(yīng)用給定的函數(shù)并把結(jié)果收集到一個(gè)新集合:

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

fun method() {
    val list = listOf(Person("Kotlin", 5), Person("Java", 20), Person("Android", 10))
    val result = list.map { it.name }
    for((index, item) in result.withIndex())
    Log.d("TAG", "zwm, index: $index, item: $item")
}

日志打印:
2020-08-24 01:03:01.554 20756-20756/com.tomorrow.kotlindemo D/TAG: zwm, index: 0, item: Kotlin
2020-08-24 01:03:01.554 20756-20756/com.tomorrow.kotlindemo D/TAG: zwm, index: 1, item: Java
2020-08-24 01:03:01.554 20756-20756/com.tomorrow.kotlindemo D/TAG: zwm, index: 2, item: Android
class Person(val name: String, val age: Int)

fun method() {
    val list = listOf(Person("Kotlin", 5), Person("Java", 20), Person("Android", 10))
    val result = list.map(Person::name)
    for((index, item) in result.withIndex())
    Log.d("TAG", "zwm, index: $index, item: $item")
}

日志打踊怯!:
2020-08-24 01:04:08.179 21530-21530/? D/TAG: zwm, index: 0, item: Kotlin
2020-08-24 01:04:08.179 21530-21530/? D/TAG: zwm, index: 1, item: Java
2020-08-24 01:04:08.179 21530-21530/? D/TAG: zwm, index: 2, item: Android

filter和map:

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

fun method() {
    val list = listOf(Person("Kotlin", 5), Person("Java", 20), Person("Android", 10))
    val result = list.filter { it.age >= 10 }.map(Person::name)
    for((index, item) in result.withIndex())
    Log.d("TAG", "zwm, index: $index, item: $item")
}

日志打幽擅ā:
2020-08-24 01:06:41.200 21911-21911/com.tomorrow.kotlindemo D/TAG: zwm, index: 0, item: Java
2020-08-24 01:06:41.200 21911-21911/com.tomorrow.kotlindemo D/TAG: zwm, index: 1, item: Android

還可以對(duì)map應(yīng)用過濾和變換函數(shù),map的鍵和值分別由各自的函數(shù)來處理竹捉,filterKeys和mapKeys過濾和變換map的鍵续担,而另外的filterValues和mapValues過濾和變換對(duì)應(yīng)的值。

fun method() {
    val numbers = mapOf(0 to "zero", 1 to "one", 2 to "two")
    Log.d("TAG", "zwm, filterKeys: ${numbers.filterKeys { it >= 1 }}")
    Log.d("TAG", "zwm, mapKeys: ${numbers.mapKeys { it.key + 10 }}")
    Log.d("TAG", "zwm, filterValues: ${numbers.filterValues { it == "two" }}")
    Log.d("TAG", "zwm, mapValues: ${numbers.mapValues { it.value + "_map" }}")
}

日志打踊詈ⅰ:
2020-08-24 01:25:09.827 25896-25896/? D/TAG: zwm, filterKeys: {1=one, 2=two}
2020-08-24 01:25:09.829 25896-25896/? D/TAG: zwm, mapKeys: {10=zero, 11=one, 12=two}
2020-08-24 01:25:09.829 25896-25896/? D/TAG: zwm, filterValues: {2=two}
2020-08-24 01:25:09.829 25896-25896/? D/TAG: zwm, mapValues: {0=zero_map, 1=one_map, 2=two_map}

"all" "any" "count" 和 "find":對(duì)集合應(yīng)用判斷式

對(duì)是否所有元素都滿足判斷式感興趣,應(yīng)該使用all函數(shù):

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

fun method() {
    val list = listOf(Person("Kotlin", 5), Person("Java", 20), Person("Android", 10))
    val result = list.all { it.age >= 5 }
    Log.d("TAG", "zwm, result: $result")
}

日志打庸猿稹:
2020-08-24 01:34:13.967 26347-26347/com.tomorrow.kotlindemo D/TAG: zwm, result: true

需要檢查集合中是否至少存在一個(gè)匹配的元素憾儒,那就用any:

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

fun method() {
    val list = listOf(Person("Kotlin", 5), Person("Java", 20), Person("Android", 10))
    val result = list.any { it.age >= 20 }
    Log.d("TAG", "zwm, result: $result")
}

日志打印:
2020-08-24 01:36:13.500 26662-26662/com.tomorrow.kotlindemo D/TAG: zwm, result: true

!all(不是所有)加上某個(gè)條件乃沙,可以用any加上這個(gè)條件的取反來替換:

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

fun method() {
    val list = listOf(Person("Kotlin", 5), Person("Java", 20), Person("Android", 10))
    val result = !list.all { it.age >= 20 }
    Log.d("TAG", "zwm, result: $result")
}

日志打悠鹬骸:
2020-08-24 01:39:54.101 27422-27422/com.tomorrow.kotlindemo D/TAG: zwm, result: true
class Person(val name: String, val age: Int)

fun method() {
    val list = listOf(Person("Kotlin", 5), Person("Java", 20), Person("Android", 10))
    val result = list.any { it.age < 20 }
    Log.d("TAG", "zwm, result: $result")
}

日志打印:
2020-08-24 01:41:00.679 27898-27898/? D/TAG: zwm, result: true

想知道有多少個(gè)元素滿足了判斷式警儒,使用count:

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

fun method() {
    val list = listOf(Person("Kotlin", 5), Person("Java", 20), Person("Android", 10))
    val result = list.count { it.age < 20 }
    Log.d("TAG", "zwm, result: $result")
}

日志打友雕伞:
2020-08-24 01:43:00.513 28113-28113/com.tomorrow.kotlindemo D/TAG: zwm, result: 2

要找到一個(gè)滿足判斷式的元素眶根,使用find函數(shù):

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

fun method() {
    val list = listOf(Person("Kotlin", 5), Person("Java", 20), Person("Android", 10))
    val result = list.find { it.age < 20 }
    Log.d("TAG", "zwm, result: $result")
}

日志打印:
2020-08-24 01:45:23.639 28734-28734/com.tomorrow.kotlindemo D/TAG: zwm, result: Kotlin

如果有多個(gè)匹配的元素就返回其中第一個(gè)元素边琉,如果沒有一個(gè)元素能滿足判斷式則返回null属百。find還有一個(gè)同義方法firstOrNull,可以使用這個(gè)方法更清楚地表述你的意圖变姨。

groupBy:把列表轉(zhuǎn)換成分組的map

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

fun method() {
    val list = listOf(Person("Kotlin", 5), Person("Java", 20), Person("Android", 10))
    val result = list.groupBy { it.age }
    for((key, value) in result) {
        Log.d("TAG", "zwm, key: $key")
        for((index, item) in value.withIndex()) {
            Log.d("TAG", "zwm, index: $index, name: ${item.name}")
        }
    }
}

日志打幼迦拧:
2020-08-24 01:57:13.505 30160-30160/com.tomorrow.kotlindemo D/TAG: zwm, key: 5
2020-08-24 01:57:13.506 30160-30160/com.tomorrow.kotlindemo D/TAG: zwm, index: 0, name: Kotlin
2020-08-24 01:57:13.506 30160-30160/com.tomorrow.kotlindemo D/TAG: zwm, key: 20
2020-08-24 01:57:13.506 30160-30160/com.tomorrow.kotlindemo D/TAG: zwm, index: 0, name: Java
2020-08-24 01:57:13.506 30160-30160/com.tomorrow.kotlindemo D/TAG: zwm, key: 10
2020-08-24 01:57:13.506 30160-30160/com.tomorrow.kotlindemo D/TAG: zwm, index: 0, name: Android

flatMap和flatten:處理嵌套集合中的元素

flatMap函數(shù)做了兩件事情:首先根據(jù)作為實(shí)參給定的函數(shù)對(duì)集合中的每個(gè)元素做變換(或者說映射),然后把多個(gè)列表合并(或者說平鋪)成一個(gè)列表:

fun method() {
    val list = listOf("abc", "def")
    val result = list.flatMap { it.toList() }
    Log.d("TAG", "zwm, result: $result")
}

日志打佣ㄅ贰:
2020-08-24 02:06:29.554 30522-30522/com.tomorrow.kotlindemo D/TAG: zwm, result: [a, b, c, d, e, f]

如果你不需要做任何變換渔呵,只是需要平鋪一個(gè)集合,可以使用flatten函數(shù):

fun method() {
    val list1 = listOf("abc", "def")
    val list2 = listOf("ghi", "jkl")
    val list = listOf(list1, list2)
    val result =  list.flatten()
    Log.d("TAG", "zwm, result: $result")
}

日志打涌仇:
2020-08-24 02:12:29.813 31375-31375/com.tomorrow.kotlindemo D/TAG: zwm, result: [abc, def, ghi, jkl]

3.惰性集合操作:序列

Kotlin標(biāo)準(zhǔn)庫(kù)參考文檔有說明:filter和map都會(huì)返回一個(gè)列表扩氢。這意味著使用filter和map的鏈?zhǔn)秸{(diào)用會(huì)創(chuàng)建兩個(gè)列表:一個(gè)保存filter函數(shù)的結(jié)果,另一個(gè)保存map函數(shù)的結(jié)果爷辱。如果源列表只有兩個(gè)元素录豺,這不是什么問題,但是如果有一百萬個(gè)元素托嚣,鏈?zhǔn)秸{(diào)用就會(huì)變得十分低效巩检。為了提高效率,可以把操作變成使用序列示启,而不是直接使用集合兢哭,這樣就不會(huì)創(chuàng)建任何用于存儲(chǔ)元素的中間集合:

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

fun method() {
    val list = listOf(Person("Apple", 5), Person("Billy", 20), Person("Android", 10))
    val result = list.asSequence() //把初始集合轉(zhuǎn)換成序列
        .map(Person::name)
        .filter { it.startsWith("A") }
        .toList() //把結(jié)果序列轉(zhuǎn)換回列表
    Log.d("TAG", "zwm, result: $result")
}

日志打印:
2020-08-24 02:32:16.517 973-973/? D/TAG: zwm, result: [Apple, Android]

Kotlin惰性集合操作的入口就是Sequence接口夫嗓,這個(gè)接口表示的就是一個(gè)可以逐個(gè)列舉元素的元素序列迟螺。Sequence只提供了一個(gè)方法,iterator舍咖,用來從序列中獲取值矩父。可以調(diào)用擴(kuò)展函數(shù)asSequence把任意集合轉(zhuǎn)換成序列排霉,調(diào)用toList來做反向的轉(zhuǎn)換窍株。

執(zhí)行序列操作:中間和末端操作

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

fun method() {
    val list = listOf(Person("Apple", 5), Person("Billy", 20), Person("Android", 10))
    val result = list.asSequence() 
        .map(Person::name) //中間操作
        .filter { it.startsWith("A") } //中間操作
        .toList() //末端操作
    Log.d("TAG", "zwm, result: $result")
}

對(duì)序列來說,所有操作是按順序應(yīng)用在每一個(gè)元素上:處理完第一個(gè)元素(先映射再過濾)攻柠,然后完成第二個(gè)元素的處理球订,以此類推。

創(chuàng)建序列

在集合上調(diào)用asSequence()可以創(chuàng)建一個(gè)序列瑰钮,另外也可以使用generateSequence函數(shù)冒滩。給定序列中的前一個(gè)元素,這個(gè)函數(shù)會(huì)計(jì)算出下一個(gè)元素:

fun method() {
    val naturalNumbers = generateSequence(0) { it + 1 }
    val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }
    val result = numbersTo100.sum() //當(dāng)獲取結(jié)果時(shí)浪谴,所有被推遲的操作都被執(zhí)行
    Log.d("TAG", "zwm, result: $result")
}

日志打涌:
2020-08-24 02:58:10.859 3913-3913/com.tomorrow.kotlindemo D/TAG: zwm, result: 5050

4.使用Java函數(shù)式接口

把lambda當(dāng)做參數(shù)傳遞給Java方法:

button.setOnClickListener { Log.d("TAG", "zwm, focusable: ${it.focusable}") }

這種方式可以工作的原因是OnClickListener接口只有一個(gè)抽象方法因苹。這種接口被稱為函數(shù)式接口,或者SAM接口篇恒,SAM代表單抽象方法扶檐。Java API中隨處可見像Runnable和Callable這樣的函數(shù)式接口,以及支持它們的方法婚度。Kotlin允許你在調(diào)用接收函數(shù)式接口作為參數(shù)的方法時(shí)使用lambda蘸秘,來保證你的Kotlin代碼既整潔又符合習(xí)慣。

使用匿名對(duì)象:

button.setOnClickListener { object : Runnable {
    override fun run() {
        Log.d("TAG", "zwm, focusable: ${it.focusable}")
    }
} }

使用匿名對(duì)象時(shí)蝗茁,每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的實(shí)例醋虏。而使用lambda時(shí),如果lambda沒有訪問任何來自定義它的函數(shù)的變量哮翘,相應(yīng)的匿名類實(shí)例可以在多次調(diào)用之間重用颈嚼;如果lambda從包圍它的作用域中捕捉了變量,每次調(diào)用就不再可能重用同一個(gè)實(shí)例了饭寺,這種情況下阻课,每次調(diào)用時(shí)編譯器都要?jiǎng)?chuàng)建一個(gè)新對(duì)象,其中存儲(chǔ)著被捕捉的變量的值艰匙。

method(1000) { println(42) } //整個(gè)程序只會(huì)創(chuàng)建一個(gè)實(shí)例

fun outMethod(id: String) {
    method(1000) { println(id) } //每次調(diào)用都會(huì)創(chuàng)建一個(gè)新實(shí)例
}

Lambda的實(shí)現(xiàn)細(xì)節(jié)

自Kotlin 1.0起限煞,每個(gè)lambda表達(dá)式都會(huì)被編譯成一個(gè)匿名類,除非它是一個(gè)內(nèi)聯(lián)lambda员凝。如果lambda捕捉了變量署驻,每個(gè)被捕捉的變量會(huì)在匿名類中有對(duì)應(yīng)的字段,而且每次對(duì)lambda的調(diào)用都會(huì)創(chuàng)建一個(gè)這個(gè)匿名類的新實(shí)例健霹。否則旺上,一個(gè)單例就會(huì)被創(chuàng)建。

SAM構(gòu)造方法:顯示地把lambda轉(zhuǎn)換成函數(shù)式接口

SAM構(gòu)造方法是編譯器生成的函數(shù)糖埋,讓你執(zhí)行從lambda到函數(shù)式接口實(shí)例的顯示轉(zhuǎn)換宣吱。可以在編譯器不會(huì)自動(dòng)應(yīng)用轉(zhuǎn)換的上下文中使用它瞳别。例如征候,如果有一個(gè)方法返回的是一個(gè)函數(shù)式接口的實(shí)例,不能直接返回一個(gè)lambda祟敛,要用SAM構(gòu)造方法把它包裝起來:

fun createAllDoneRunnable(): Runnable {
    return Runnable { println("All done!") }
}

fun method() {
    createAllDoneRunnable().run()
}

SAM構(gòu)造方法的名稱和底層函數(shù)式接口的名稱一樣倍奢。SAM構(gòu)造方法只接收一個(gè)參數(shù):一個(gè)被用作函數(shù)式接口單抽象方法體的lambda,并返回實(shí)現(xiàn)了這個(gè)接口的類的一個(gè)實(shí)例垒棋。

除了返回值外,SAM構(gòu)造方法還可以用在需要把從lambda生成的函數(shù)式接口實(shí)例存儲(chǔ)在一個(gè)變量中的情況:

val listener = OnClickListener { view -> //使用SAM構(gòu)造方法來重用listener實(shí)例
    val text = when (view.id) { //根據(jù)view.id來判斷點(diǎn)擊的是哪個(gè)按鈕
        R.id.button1 -> "First button"
        R.id.button2 -> "Second button"
        else -> "Unknown button"
    }   
    toast(text)
}
button1.setOnClickListener(listener)
button2.setOnClickListener(listener)

Lambda和添加/移除監(jiān)聽器

注意lambda內(nèi)部沒有匿名對(duì)象那樣的this:沒有辦法引用到lambda轉(zhuǎn)換成的匿名類實(shí)例痪宰。從編譯器的角度來看叼架,lambda是一個(gè)代碼塊畔裕,不是一個(gè)對(duì)象,而且也不能把它當(dāng)成對(duì)象引用乖订。Lambda中的this引用指向的是包圍它的類扮饶。如果你的事件監(jiān)聽器在處理事件時(shí)還需要取消它自己,不能使用lambda這樣做乍构。這種情況使用實(shí)現(xiàn)了接口的匿名對(duì)象甜无。在匿名對(duì)象內(nèi),this關(guān)鍵字指向該對(duì)象實(shí)例哥遮,可以把它傳給移除監(jiān)聽器的API岂丘。

5.帶接收者的lambda:with與apply

with函數(shù):

fun method() {
    val stringBuilder = StringBuilder()
    val result = with(stringBuilder) { //指定接收者的值,你會(huì)調(diào)用它的方法
        for (letter in 'A'..'Z') {
            this.append(letter) //通過顯式的this來調(diào)用接收者值的方法
        }
        append("done") //省掉this也可以調(diào)用接收者值的方法
        this.toString()
    }
    Log.d("TAG", "zwm, result: $result")
}

日志打用咭:
2020-08-24 15:14:55.028 1709-1709/com.tomorrow.kotlindemo D/TAG: zwm, result: ABCDEFGHIJKLMNOPQRSTUVWXYZdone

with結(jié)構(gòu)看起來像是一種特殊的語法結(jié)構(gòu)奥帘,但它實(shí)際上是一個(gè)接收兩個(gè)參數(shù)的函數(shù):以上例子中兩個(gè)參數(shù)分別是stringBuilder和一個(gè)lambda。這里利用了把lambda放在括號(hào)外的約定仪召,這樣整個(gè)調(diào)用看起來就像是內(nèi)建的語言功能寨蹋。with函數(shù)把它的第一個(gè)參數(shù)轉(zhuǎn)換成作為第二個(gè)參數(shù)傳給它的lambda的接收者∪用可以顯式地通過this引用來訪問這個(gè)接收者已旧,也可以省略this引用,不用任何限定符直接訪問這個(gè)值的方法和屬性召娜。

帶接收者的lambda和擴(kuò)展函數(shù)

在擴(kuò)展函數(shù)體內(nèi)部运褪,this指向了這個(gè)函數(shù)擴(kuò)展的那個(gè)類型的實(shí)例,而且也可以被省略掉萤晴,讓你直接訪問接收者的成員吐句。注意一個(gè)擴(kuò)展函數(shù)某種意義上來說就是帶接收者的函數(shù)。

方法名稱沖突:

class Outer(val name: String) {
    fun method() {
        val stringBuilder = StringBuilder()
        val result = with(stringBuilder) {
            append(this@Outer.toString()) //使用this@Outer引用外部類實(shí)例
            append("done")
            this.toString() //使用this引用接收者實(shí)例
        }
        Log.d("TAG", "zwm, result: $result")
    }

    override fun toString(): String {
        return "I am Outer"
    }
}

日志打拥甓痢:
2020-08-24 15:37:16.150 3594-3594/com.tomorrow.kotlindemo D/TAG: zwm, result: I am Outerdone

apply函數(shù):

with函數(shù)返回的值是執(zhí)行了lambda代碼的結(jié)果嗦枢,該結(jié)果就是lambda中的最后一個(gè)表達(dá)式的值。apply函數(shù)幾乎和with函數(shù)一模一樣屯断,唯一的區(qū)別是apply始終會(huì)返回作為實(shí)參傳遞給它的對(duì)象(即接收者對(duì)象)文虏。

fun method() {
    val stringBuilder = StringBuilder()
    val result = stringBuilder.apply {
        for (letter in 'A'..'Z') {
            this.append(letter)
        }
        append("done")
        this.toString()
    }.toString()
    Log.d("TAG", "zwm, result: $result")
}

日志打忧⒃恪:
2020-08-24 15:43:16.246 4227-4227/com.tomorrow.kotlindemo D/TAG: zwm, result: ABCDEFGHIJKLMNOPQRSTUVWXYZdone

apply被聲明成一個(gè)擴(kuò)展函數(shù)末贾,它的接收者變成了作為實(shí)參的lambda的接收者锦秒。在Kotlin中绕沈,可以在任意對(duì)象上使用apply骡湖,完全不需要任何來自定義該對(duì)象的庫(kù)的特別支持廷臼。

with函數(shù)和apply函數(shù)是最基本和最通用的使用帶接收者的lambda的例子拌蜘,更多具體的函數(shù)也可以使用這種模式竟秫。例如彼棍,可以使用標(biāo)準(zhǔn)庫(kù)函數(shù)buildString灭忠,它會(huì)負(fù)責(zé)創(chuàng)建StringBuilder并調(diào)用toString膳算。buildString的實(shí)參是一個(gè)帶接收者的lambda,接收者就是StringBuilder:

fun method() {
    val result = buildString {
        for (letter in 'A'..'Z') {
            append(letter)
        }
        append("done")
    }
    Log.d("TAG", "zwm, result: $result")
}

日志打映谧鳌:
2020-08-24 15:55:52.341 4814-4814/com.tomorrow.kotlindemo D/TAG: zwm, result: ABCDEFGHIJKLMNOPQRSTUVWXYZdone

六涕蜂、Kotlin的類型系統(tǒng)

1.可空性

Kotlin的類型結(jié)構(gòu):

Kotlin的類型結(jié)構(gòu)

Kotlin和Java的類型系統(tǒng)之間第一條也可能是最重要的一條區(qū)別是,Kotlin對(duì)可空類型的顯式的支持映琳。問號(hào)可以加在任何類型的后面來表示這個(gè)類型的變量可以存儲(chǔ)null引用机隙,例如:String?、Int?萨西、MyCustomType? 等等有鹿。

Type? = Type or null

沒有問號(hào)的類型表示這種類型的變量不能存儲(chǔ)null引用。這說明所有常見類型默認(rèn)都是非空的原杂,除非顯式地把它標(biāo)記為可空印颤。

一旦你有一個(gè)可空類型的值,能對(duì)它進(jìn)行的操作也會(huì)受到限制:

fun method(str: String?) {
    val length = str.length //編譯錯(cuò)誤穿肄,str可能為null
    val x: String? = null //正確年局,x可以為null
    val y: String = x //編譯錯(cuò)誤,y不可以為null
}

安全調(diào)用運(yùn)算符:咸产?矢否,允許你把一次null檢查和一次調(diào)用合并成一個(gè)操作:

fun method() {
    val str : String? = null
    val length = str?.length //訪問屬性
    val ch = str?.get(0) //調(diào)用方法
    Log.d("TAG", "zwm, length: $length, ch: $ch")
}

日志打印:
2020-08-24 19:50:28.066 25302-25302/com.tomorrow.kotlindemo D/TAG: zwm, length: null, ch: null

Elvis運(yùn)算符:?:脑溢,也叫做null合并運(yùn)算符僵朗,接收兩個(gè)運(yùn)算數(shù),如果第一個(gè)運(yùn)算數(shù)不為null屑彻,運(yùn)算結(jié)果就是第一個(gè)運(yùn)算數(shù)验庙,如果第一個(gè)運(yùn)算數(shù)為null,運(yùn)算結(jié)果就是第二個(gè)運(yùn)算數(shù):

fun method() {
    val str : String? = null
    val length = str?.length ?: 0
    Log.d("TAG", "zwm, length: $length")
}

日志打由缟:
2020-08-24 20:04:06.707 2443-2443/com.tomorrow.kotlindemo D/TAG: zwm, length: 0

安全轉(zhuǎn)換:as粪薛?,嘗試把值轉(zhuǎn)換成指定的類型搏恤,如果值不是合適的類型就返回null:

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

fun method() {
    val str: String? = "hello"
    val result = str as? Person
    Log.d("TAG", "zwm, result: $result")
}

日志打游ナ佟:
2020-08-24 21:03:33.696 18222-18222/com.tomorrow.kotlindemo D/TAG: zwm, result: null

非空斷言:!!,使用雙感嘆號(hào)表示熟空,可以把任何值轉(zhuǎn)換成非空類型藤巢,如果對(duì)null值做非空斷言,則會(huì)拋出異常:

fun method() {
    val str: String? = null
    val result = str!!.length
    Log.d("TAG", "zwm, result: $result")
}

日志打酉⒙蕖:
kotlin.KotlinNullPointerException

let函數(shù)掂咒,讓處理可空表達(dá)式變得更容易。和安全調(diào)用運(yùn)算符一起,它允許你對(duì)表達(dá)式求值绍刮,檢查求值結(jié)果是否為null糜工,并把結(jié)果保存為一個(gè)變量,所有這些動(dòng)作都在同一個(gè)簡(jiǎn)潔的表達(dá)式中:

fun method() {
    val str: String? = "Kotlin"
    str?.let { Log.d("TAG", "zwm, value: $it") }
}

日志打勇嫉:
2020-08-24 21:48:55.457 25465-25465/com.tomorrow.kotlindemo D/TAG: zwm, value: Kotlin

延遲初始化的屬性

延遲初始化的屬性都是var,因?yàn)樾枰跇?gòu)造方法外修改它的值油坝,而val屬性會(huì)被編譯成必須在構(gòu)造方法中初始化的final字段:

class Person(val name: String) {
    lateinit var parent: Person //延遲初始化屬性

    fun initPerson() {
        val father = Person("Father")
        parent = father //延遲初始化
        Log.d("TAG", "zwm, name: ${name} ${parent.name}")
    }
}

fun method() {
    val person = Person("Happy")
    person.initPerson()
}

日志打蛹灯荨:
2020-08-24 22:09:14.567 26769-26769/com.tomorrow.kotlindemo D/TAG: zwm, name: Happy Father

可空類型的擴(kuò)展

為可空類型定義擴(kuò)展函數(shù)是一種更強(qiáng)大的處理null值的方式〕喝Γ可以允許接收者為null的擴(kuò)展函數(shù)調(diào)用彬檀,并在該函數(shù)中處理null,而不是在確保變量不為null之后再調(diào)用它的方法瞬女。只有擴(kuò)展函數(shù)才能做到這一點(diǎn)窍帝,普通成員方法的調(diào)用是通過對(duì)象實(shí)例來分發(fā)的,因此實(shí)例為null時(shí)成員方法永遠(yuǎn)不能被執(zhí)行诽偷。

函數(shù)isEmptyOrNull和isNullOrBlank就可以由String?類型的接收者調(diào)用:(不需要安全調(diào)用@ぱА)

fun method() {
    val str: String? = " "
    val str2: String? = null
    Log.d("TAG", "zwm, ${str.isNullOrBlank()} ${str.isNullOrEmpty()}")
    Log.d("TAG", "zwm, ${str2.isNullOrBlank()} ${str2.isNullOrEmpty()}")
}

日志打印:
2020-08-24 22:40:13.402 28472-28472/com.tomorrow.kotlindemo D/TAG: zwm, true false
2020-08-24 22:40:13.402 28472-28472/com.tomorrow.kotlindemo D/TAG: zwm, true true
fun String?.isNullOrBlank(): Boolean = //可空字符串的擴(kuò)展
    this == null || this.isBlank() //第二個(gè)this使用了智能轉(zhuǎn)換

當(dāng)你為一個(gè)可空類型(以?結(jié)尾)定義擴(kuò)展函數(shù)時(shí)报慕,這意味著你可以對(duì)可空的值調(diào)用這個(gè)函數(shù)深浮,并且函數(shù)體中的this可能為null,所以你必須顯式地檢查眠冈。

類型參數(shù)的可空性

Kotlin中所有泛型類和泛型函數(shù)的類型參數(shù)默認(rèn)都是可空的飞苇。任何類型,包括可空類型在內(nèi)蜗顽,都可以替換類型參數(shù)布卡。這種情況下,使用類型參數(shù)作為類型的聲明都允許為null雇盖,盡管類型參數(shù)T并沒有用問號(hào)結(jié)尾:

fun <T> printHashCode(t: T) { //類型參數(shù)T推導(dǎo)出的類型是可空類型Any?
    Log.d("TAG", "zwm, ${t?.hashCode()}") //因?yàn)閠可能為null忿等,所以必須使用安全調(diào)用
}

fun method() {
    printHashCode("abc")
}

日志打印:
2020-08-24 23:04:13.388 30688-30688/com.tomorrow.kotlindemo D/TAG: zwm, 96354

要使類型參數(shù)非空刊懈,必須要為它指定一個(gè)非空的上界这弧,那樣泛型會(huì)拒絕可空值作為實(shí)參:

fun <T: Any> printHashCode(t: T) { //類型參數(shù)T推導(dǎo)出的類型是可空類型Any
    Log.d("TAG", "zwm, ${t.hashCode()}") //因?yàn)閠不可能為null,所以不需要使用安全調(diào)用
}

fun method() {
    printHashCode("abc")
}

日志打有檠础:
2020-08-24 23:09:11.924 31193-31193/com.tomorrow.kotlindemo D/TAG: zwm, 96354

注意必須使用問號(hào)結(jié)尾來標(biāo)記類型為可空的匾浪,沒有問號(hào)就是非空的。類型參數(shù)是這個(gè)規(guī)則唯一的例外卷哩。

可空性和Java

Java中的@Nullable String被Kotlin當(dāng)做String?蛋辈,而@NotNull String就是String。

Java類型在Kotlin中表示為平臺(tái)類型,既可以把它當(dāng)作可空類型也可以當(dāng)作非空類型來處理冷溶。這意味著渐白,你要像在Java中一樣,對(duì)你在這個(gè)類型上做的操作負(fù)有全部責(zé)任逞频。

當(dāng)通過繼承在Kotlin中重寫Java的方法時(shí)纯衍,可以選擇把參數(shù)和返回類型定義成可空的,也可以選擇把它們定義成非空的苗胀。注意襟诸,在實(shí)現(xiàn)Java類或者接口的方法時(shí)一定要搞清楚它的可空性,因?yàn)榉椒ǖ膶?shí)現(xiàn)可以在非Kotlin的代碼中被調(diào)用基协。

2.基本數(shù)據(jù)類型和其他基本類型

基本數(shù)據(jù)類型:Int歌亲、Boolean及其他

Kotlin并不區(qū)分基本數(shù)據(jù)類型和包裝類型,你使用的永遠(yuǎn)是同一個(gè)類型澜驮。大多數(shù)情況下陷揪,對(duì)于變量、屬性杂穷、參數(shù)和返回類型悍缠,Kotlin的Int類型會(huì)被編譯成Java基本數(shù)據(jù)類型int。唯一不可行的例外是泛型類亭畜,比如集合扮休,用作泛型類型參數(shù)的基本數(shù)據(jù)類型會(huì)被編譯成對(duì)應(yīng)的Java包裝類型,Kotlin的Int類型會(huì)被編譯成Java包裝類型java.lang.Integer拴鸵。

val i: Int = 1 //編譯成基本數(shù)據(jù)類型int
val list: List<Int> = listOf(1, 2, 3) //編譯成包裝類型java.lang.Integer

可空的基本數(shù)據(jù)類型:Int?玷坠、Boolean?及其他

Kotlin中的可空類型不能用Java的基本數(shù)據(jù)類型表示,因?yàn)閚ull只能被存儲(chǔ)在Java的引用類型的變量中劲藐。這意味著任何時(shí)候只要使用了基本數(shù)據(jù)類型的可空版本八堡,它就會(huì)編譯成對(duì)應(yīng)的包裝類型。

val i: Int? = 1 //編譯成包裝類型java.lang.Integer

數(shù)字轉(zhuǎn)換

Kotlin不會(huì)自動(dòng)地把數(shù)字從一種類型轉(zhuǎn)換成另外一種聘芜,即便是轉(zhuǎn)換成范圍更大的類型兄渺,Kotlin要求轉(zhuǎn)換必須是顯式的:

val i = 1
val l: Long = i //編譯錯(cuò)誤:類型不匹配
val l: Long = i.toLong() //正確

對(duì)應(yīng)到Java基本數(shù)據(jù)類型的類型完整列表如下:

整數(shù)類型:Byte、Short汰现、Int挂谍、Long
浮點(diǎn)數(shù)類型:Float、Double
字符類型:Char
布爾類型:Boolean

基本數(shù)據(jù)類型字面值:

Long:123L
Double:0.12瞎饲、2.0口叙、1.2e10、1.2e-10
Float:123.4f嗅战、.456F妄田、1e3f
十六機(jī)制:0xCAFEBABE俺亮、0xbcdL
二進(jìn)制:0b000000101

字符串轉(zhuǎn)換

Kotlin標(biāo)準(zhǔn)庫(kù)提供了一套相似的擴(kuò)展方法,用來把字符串轉(zhuǎn)換成基本數(shù)據(jù)類型:toInt疟呐、toByte脚曾、toBoolean等,每個(gè)這樣的函數(shù)都會(huì)嘗試把字符串的內(nèi)容解析成對(duì)應(yīng)的類型启具,如果解析失敗則拋出NumberFormatException本讥。

Any和Any?:根類型

Any類型是Kotlin所有非空類型的超類型。當(dāng)Kotlin函數(shù)使用Any時(shí)鲁冯,它會(huì)被編譯成Java字節(jié)碼中的Object囤踩。所有Kotlin類都包含下面三個(gè)方法:toString、equals和hashCode晓褪,這些方法都繼承自Any。Any并不能使用其他java.lang.Object的方法(比如wait和notify)综慎,但是可以通過手動(dòng)把值轉(zhuǎn)換成java.lang.Object來調(diào)用這些方法涣仿。

Unit類型:Kotlin的void

Unit是一個(gè)完備的類型,可以作為類型參數(shù)示惊,而void卻不行好港。只存在一個(gè)值是Unit類型,這個(gè)值也叫作Unit米罚,并且在函數(shù)中會(huì)被隱式地返回钧汹。當(dāng)你在重寫返回泛型參數(shù)的函數(shù)時(shí)這非常有用,只需要讓方法返回Unit類型的值:

interface Processor<T> {
    fun process(): T
}

class NoResultProcessor : Processor<Unit> { //使用Unit作為類型參數(shù)
    override fun process() { //返回Unit录择,但可以省略類型說明
        //do stuff //這里不需要顯式的return
    }
}

Nothing類型:這個(gè)函數(shù)永不返回

Nothing類型沒有任何值拔莱,只有被當(dāng)作函數(shù)返回值使用,或者被當(dāng)作泛型函數(shù)返回值的類型參數(shù)使用才會(huì)有意義隘竭。在其他所有情況下塘秦,聲明一個(gè)不能存儲(chǔ)任何值的變量沒有任何意義。

fun fail(message: String): Nothing {
    throw IllegalStateException(message)
}

fun method() {
    fail("I am fail")
}

日志打佣础:
java.lang.IllegalStateException: I am fail

3.集合與數(shù)組

Kotlin以Java集合庫(kù)為基礎(chǔ)構(gòu)建尊剔,并通過擴(kuò)展函數(shù)增加的特性來增強(qiáng)它。

集合的繼承關(guān)系:

集合的繼承關(guān)系

可空性和集合

Kotlin支持類型參數(shù)的可空性:

fun method() {
    val list = ArrayList<Int?>()
    list.add(999)
    list.add(666)
    list.add(888)
    list.add(null)
    Log.d("TAG", "zwm, list: $list")
}

日志打恿饨浴:
2020-08-25 02:04:55.476 9146-9146/com.tomorrow.kotlindemo D/TAG: zwm, list: [999, 666, 888, null]

聲明一個(gè)變量持有可空的列表须误,并且包含可空的數(shù)字:List<Int?>?,有兩個(gè)問號(hào)仇轻,使用變量自己的值的時(shí)候京痢,以及使用列表中每個(gè)元素的值的時(shí)候,都需要使用null檢查拯田。

Kotlin提供了一個(gè)標(biāo)準(zhǔn)庫(kù)函數(shù)filterNotNull用來遍歷一個(gè)包含可空值的集合并過濾掉null历造,這種過濾也影響了集合的類型:

fun method() {
    val list = ArrayList<Int?>()
    list.add(999)
    list.add(666)
    list.add(888)
    list.add(null)
    Log.d("TAG", "zwm, list: ${list.filterNotNull()}")
}

日志打印:
2020-08-25 02:09:59.072 9852-9852/com.tomorrow.kotlindemo D/TAG: zwm, list: [999, 666, 888]

只讀集合與可變集合:

Kotlin把訪問集合數(shù)據(jù)的接口和修改集合數(shù)據(jù)的接口分開了,kotlin.collections.Collection接口可以遍歷集合中的元素、獲取集合大小颁督、判斷集合中是否包含某個(gè)元素劈彪,以及執(zhí)行其他從該集合中讀取數(shù)據(jù)的操作。但這個(gè)接口沒有任何添加或移除元素的方法橄霉。

使用kotlin.collections.MutableCollection接口可以修改集合中的數(shù)據(jù)邑蒋。它繼承了普通的kotlin.collections.Collection接口医吊,還提供了方法來添加和移除元素饿敲、清空集合等沥曹。

fun <T> copyElements(source: Collection<T>, target: MutableCollection<T>) {
    for(item in source) {
        target.add(item)
    }
}

fun method() {
    val source: Collection<Int> = arrayListOf(3, 5, 7)
    val target: MutableCollection<Int> = arrayListOf(1)
    copyElements(source, target)
    Log.d("TAG", "zwm, target: $target")
}

日志打恿鸲怠:
2020-08-25 02:24:42.891 10690-10690/com.tomorrow.kotlindemo D/TAG: zwm, target: [1, 3, 5, 7]

使用集合接口時(shí)需要牢記的一個(gè)關(guān)鍵點(diǎn)是只讀集合不一定是不可變的该互。如果你使用的變量擁有一個(gè)只讀接口類型隘马,它可能只是同一個(gè)集合的眾多引用中的一個(gè)钝鸽,任何其他的引用都可能擁有一個(gè)可變接口類型风皿。

Kotlin集合和Java

集合創(chuàng)建函數(shù):

集合類型 只讀 可變
List listOf mutableListOf袋励、arrayListOf
Set setOf mutableSetOf、hashSetOf吴旋、linkedSetOf项棠、sortedSetOf
Map mapOf mutableMapOf峭咒、hashMapOf凑队、linkedMapOf则果、sortedMapOf

Java并不會(huì)區(qū)分只讀集合與可變集合,即使Kotlin中把集合聲明成只讀的顽决,Java代碼也能夠修改這個(gè)集合。

作為平臺(tái)類型的集合

Kotlin把那些定義在Java代碼中的類型看成平臺(tái)類型导匣,Kotlin沒有任何關(guān)于平臺(tái)類型的可空性信息才菠,所以編譯器允許Kotlin代碼將其視為可空或非空。同樣贡定,Java中聲明的集合類型的變量也被視為平臺(tái)類型赋访,一個(gè)平臺(tái)類型的集合本質(zhì)上就是可變性未知的集合,Kotlin代碼將其視為只讀的或者可變的缓待。

對(duì)象和基本數(shù)據(jù)類型的數(shù)組

要在Kotlin中創(chuàng)建數(shù)組蚓耽,有下面這些方法:

  • arrayOf函數(shù)創(chuàng)建一個(gè)數(shù)組,它包含的元素是指定為該函數(shù)的實(shí)參旋炒。
  • arrayOfNulls創(chuàng)建一個(gè)給定大小的數(shù)組步悠,包含的是null元素。當(dāng)然瘫镇,它只能用來創(chuàng)建包含元素類型可空的數(shù)組鼎兽。
  • Array構(gòu)造方法接收數(shù)組的大小和一個(gè)lambda表達(dá)式答姥,調(diào)用lambda表達(dá)式來創(chuàng)建每一數(shù)組元素。這就是使用非空元素類型來初始化數(shù)組谚咬,但不用顯式地傳遞每個(gè)元素的方式鹦付。

Kotlin中的數(shù)組是支持泛型的,當(dāng)然也不再協(xié)變择卦,也就是說你不能將任意一個(gè)對(duì)象數(shù)組賦值給Array<Any>或者Array<Any?>敲长。

fun method() {
    val arr = arrayOf(3, 5, 7)
    val arr2 = arrayOfNulls<Int>(3)
    val arr3 = Array(3) { 100 + it }
    for (i in arr.indices) {
        Log.d("TAG", "zwm, ${arr[i]}")
    }
    for (i in arr2.indices) {
        Log.d("TAG", "zwm, ${arr2[i]}")
    }
    for (i in arr3.indices) {
        Log.d("TAG", "zwm, ${arr3[i]}")
    }
}

日志打印:
2020-08-25 03:11:50.443 14918-14918/? D/TAG: zwm, 3
2020-08-25 03:11:50.443 14918-14918/? D/TAG: zwm, 5
2020-08-25 03:11:50.443 14918-14918/? D/TAG: zwm, 7
2020-08-25 03:11:50.443 14918-14918/? D/TAG: zwm, null
2020-08-25 03:11:50.443 14918-14918/? D/TAG: zwm, null
2020-08-25 03:11:50.444 14918-14918/? D/TAG: zwm, 100
2020-08-25 03:11:50.444 14918-14918/? D/TAG: zwm, 101
2020-08-25 03:11:50.444 14918-14918/? D/TAG: zwm, 102

向vararg方法傳遞集合:

fun method() {
    val strings = listOf("a", "b", "c")
    val result = format("%s/%s/%s", *strings.toTypedArray()) //期望vararg參數(shù)時(shí)使用展開運(yùn)算符(*)傳遞數(shù)組
    Log.d("TAG", "zwm, result: $result")
}

日志打颖獭:
2020-08-25 03:17:48.876 15174-15174/com.tomorrow.kotlindemo D/TAG: zwm, result: a/b/c

數(shù)組類型的類型參數(shù)始終會(huì)變成對(duì)象類型祈噪。因此,如果你聲明了一個(gè)Array<Int>秕噪,它將會(huì)是一個(gè)包含裝箱整型的數(shù)組(它的Java類型將是java.lang.Integer[])钳降。如果你需要?jiǎng)?chuàng)建沒有裝箱的基本數(shù)據(jù)類型的數(shù)組,必須使用一個(gè)基本數(shù)據(jù)類型數(shù)組的特殊類腌巾。

為了表示基本數(shù)據(jù)類型的數(shù)組遂填,Kotlin提供了若干獨(dú)立的類,每一種基本數(shù)據(jù)類型都對(duì)應(yīng)一個(gè)澈蝙。例如:IntArray吓坚、ByteArray、CharArray灯荧、BooleanArray等礁击,所有這些類型都被編譯成普通的Java基本數(shù)據(jù)類型數(shù)組,比如int[]逗载、byte[]哆窿、char[]、boolean[]等厉斟,因此這些數(shù)組中的值存儲(chǔ)時(shí)并沒有裝箱挚躯,而是使用了可能的最高效的方式。

要?jiǎng)?chuàng)建一個(gè)基本數(shù)據(jù)類型的數(shù)組擦秽,有如下方法:

  • 該類型的構(gòu)造方法接收size參數(shù)并返回一個(gè)使用對(duì)應(yīng)基本數(shù)據(jù)類型默認(rèn)值(通常是0)初始化好的數(shù)組码荔。
  • 工廠函數(shù)(IntArray的intArrayOf,以及其他數(shù)組類型的函數(shù))接收變長(zhǎng)參數(shù)的值并創(chuàng)建存儲(chǔ)這些值的數(shù)組感挥。
  • 另一種構(gòu)造方法缩搅,接收一個(gè)大小和一個(gè)用來初始化每個(gè)元素的lambda。
fun method() {
    val arr = IntArray(3)
    val arr2 = intArrayOf(0, 0, 0)
    val arr3 = IntArray(3) { it + 10 }
    for (i in arr.indices) {
        Log.d("TAG", "zwm1, ${arr[i]}")
    }
    for (i in arr2.indices) {
        Log.d("TAG", "zwm2, ${arr2[i]}")
    }
    for (i in arr3.indices) {
        Log.d("TAG", "zwm3, ${arr3[i]}")
    }
}

日志打哟ビ住:
2020-08-25 03:42:28.303 18958-18958/com.tomorrow.kotlindemo D/TAG: zwm1, 0
2020-08-25 03:42:28.304 18958-18958/com.tomorrow.kotlindemo D/TAG: zwm1, 0
2020-08-25 03:42:28.304 18958-18958/com.tomorrow.kotlindemo D/TAG: zwm2, 0
2020-08-25 03:42:28.304 18958-18958/com.tomorrow.kotlindemo D/TAG: zwm2, 0
2020-08-25 03:42:28.304 18958-18958/com.tomorrow.kotlindemo D/TAG: zwm3, 10
2020-08-25 03:42:28.305 18958-18958/com.tomorrow.kotlindemo D/TAG: zwm3, 11
2020-08-25 03:42:28.305 18958-18958/com.tomorrow.kotlindemo D/TAG: zwm3, 12

如果有一個(gè)持有基本數(shù)據(jù)類型裝箱后的數(shù)組或者集合硼瓣,可以用對(duì)應(yīng)的轉(zhuǎn)換函數(shù)把它們轉(zhuǎn)換成基本數(shù)據(jù)類型的數(shù)組,比如toIntArray置谦。

對(duì)于數(shù)組巨双,除了那些基本操作(獲取數(shù)組的長(zhǎng)度噪猾,獲取或者設(shè)置元素)外,Kotlin標(biāo)準(zhǔn)庫(kù)支持一套和集合相同的用于數(shù)組的擴(kuò)展函數(shù)筑累,如filter袱蜡、map等也適用于數(shù)組,包括基本數(shù)據(jù)類型的數(shù)組(注意慢宗,這些方法的返回值是列表而不是數(shù)組)坪蚁。

對(duì)數(shù)組使用forEachIndexed:

fun method() {
    val arr = IntArray(3) { it + 10 }
    arr.forEachIndexed { index, i -> Log.d("TAG", "zwm, $index:$i") }
}

日志打印:
2020-08-25 03:51:16.075 19707-19707/com.tomorrow.kotlindemo D/TAG: zwm, 0:10
2020-08-25 03:51:16.075 19707-19707/com.tomorrow.kotlindemo D/TAG: zwm, 1:11
2020-08-25 03:51:16.076 19707-19707/com.tomorrow.kotlindemo D/TAG: zwm, 2:12

七镜沽、運(yùn)算符重載及其他約定

1.重載算術(shù)運(yùn)算符

重載二元算術(shù)運(yùn)算符:

data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point { //定義一個(gè)名為plus的方法
        return Point(x + other.x, y + other.y)
    }
}

fun method() {
    val p1 = Point(200, 300)
    val p2 = Point(300, 200)
    val p = p1 + p2
    Log.d("TAG", "zwm, x: ${p.x} y: ${p.y}")
}

日志打用粑睢:
2020-08-06 14:43:15.876 25513-25513/com.tomorrow.kotlindemo D/TAG: zwm, x: 500 y: 500

把運(yùn)算符定義為擴(kuò)展函數(shù):

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

operator fun Point.plus(other: Point): Point {
    return Point(x + other.x, y + other.y)
}

fun method() {
    val p1 = Point(200, 300)
    val p2 = Point(300, 200)
    val p = p1 + p2
    Log.d("TAG", "zwm, x: ${p.x} y: ${p.y}")
}

日志打印:
2020-08-06 14:57:13.529 26210-26210/com.tomorrow.kotlindemo D/TAG: zwm, x: 500 y: 500

可重載的二元算術(shù)運(yùn)算符:

表達(dá)式 函數(shù)名
a * b times
a / b div
a % b mod
a + b plus
a - b minus

定義一個(gè)運(yùn)算數(shù)類型不同的運(yùn)算符:

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

operator fun Point.times(scale: Double): Point {
    return Point((x * scale).toInt(), (y * scale).toInt())
}

fun method() {
    val p = Point(200, 300) * 1.5
    Log.d("TAG", "zwm, x: ${p.x} y: ${p.y}")
}

日志打用遘浴:
2020-08-06 15:09:11.415 27811-27811/com.tomorrow.kotlindemo D/TAG: zwm, x: 300 y: 450

定義一個(gè)返回結(jié)果不同的運(yùn)算符:

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

operator fun Point.times(scale: Double): String {
    return "${(x * scale).toInt()}, ${(y * scale).toInt()}"
}

fun method() {
    val result = Point(200, 300) * 1.5
    Log.d("TAG", "zwm, result: $result")
}

日志打幼炱ⅰ:
2020-08-06 15:14:35.870 28822-28822/com.tomorrow.kotlindemo D/TAG: zwm, result: 300, 450

Kotlin沒有為標(biāo)準(zhǔn)數(shù)字類型定義任何位運(yùn)算符,因此蔬墩,也不允許你為自定義類型定義它們译打。相反,它使用支持中綴調(diào)用語法的常規(guī)函數(shù)拇颅,可以為自定義類型定義相似的函數(shù)奏司。

Kotlin中用于執(zhí)行位運(yùn)算的函數(shù)列表:

  • shl:帶符號(hào)左移
  • shr:帶符號(hào)右移
  • ushr:無符號(hào)右移
  • and:按位與
  • or:按位或
  • xor:按位異或
  • inv:按位取反
fun method() {
    val result1 = 0x0F and 0xF0
    val result2 = 0x0F or 0xF0
    val result3 = 0x1 shl 4
    Log.d("TAG", "zwm, $result1 $result2 $result3")
}

日志打印:
2020-08-06 15:28:50.149 29868-29868/com.tomorrow.kotlindemo D/TAG: zwm, 0 255 16

重載復(fù)合賦值運(yùn)算符:(plusAssign樟插、minusAssign韵洋、timesAssign等)

data class Point(var x: Int, var y: Int)

operator fun Point.plusAssign(other: Point) {
    x += other.x
    y += other.y
}

fun method() {
    val p = Point(200, 300)
    p.plusAssign(Point(300, 200))
    Log.d("TAG", "zwm, $p")
}

日志打印:
2020-08-06 15:45:40.161 31500-31500/com.tomorrow.kotlindemo D/TAG: zwm, Point(x=500, y=500)

重載一元運(yùn)算符:

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

operator fun Point.unaryMinus(): Point {
    return Point(-x, -y)
}

fun method() {
    val p = Point(200, 300).unaryMinus()
    Log.d("TAG", "zwm, $p")
}

日志打踊拼浮:
2020-08-06 15:51:35.020 31955-31955/com.tomorrow.kotlindemo D/TAG: zwm, Point(x=-200, y=-300)

可重載的一元算法的運(yùn)算符:

表達(dá)式 函數(shù)名
+a unaryPlus
-a unaryMinus
!a not
++a搪缨,a++ inc
--a,a-- dec

2.重載比較運(yùn)算符

與算術(shù)運(yùn)算符一樣鸵熟,在Kotlin中副编,可以對(duì)任何對(duì)象使用比較運(yùn)算符(==、!=旅赢、>齿桃、<等)惑惶,而不僅僅限于基本數(shù)據(jù)類型煮盼。

等號(hào)運(yùn)算符:equals

如果在Kotlin中使用==運(yùn)算符,它將被轉(zhuǎn)換成equals方法的調(diào)用带污。使用!=運(yùn)算符也會(huì)被轉(zhuǎn)換成equals函數(shù)的調(diào)用僵控,明顯的差異在于,它們的結(jié)果是相反的鱼冀。注意报破,和所有其他運(yùn)算符不同的是悠就,==和!=可以用于可空運(yùn)算數(shù),因?yàn)檫@些運(yùn)算符事實(shí)上會(huì)檢查運(yùn)算數(shù)是否為null充易。比較a==b會(huì)檢查a是否為非空梗脾,如果是,就調(diào)用a.equals(b)盹靴,否則炸茧,只有兩個(gè)參數(shù)都是空引用,結(jié)果才是true稿静。

a == b -> a?.equals(b) ?: (b == null)
data class Point(val x: Int, val y: Int) {
    override fun equals(other: Any?): Boolean {
        if(other === this) return true //優(yōu)化:檢查參數(shù)是否與this是同一個(gè)對(duì)象
        if(other !is Point) return false //檢查參數(shù)類型
        return other.x == x && other.y == y //智能轉(zhuǎn)換為Point來訪問x梭冠、y屬性
    }
}

fun method() {
    val p1 = Point(200, 300)
    val p2 = Point(200, 300)
    val result = p1 == p2
    val result2 = null == p1
    Log.d("TAG", "zwm, result: $result result2: $result2")
}

日志打印:
2020-08-06 16:31:21.888 7153-7153/com.tomorrow.kotlindemo D/TAG: zwm, result: true result2: false

恒等運(yùn)算符(===)與Java中的==運(yùn)算符是完全相同的:檢查兩個(gè)參數(shù)是否是同一個(gè)對(duì)象的引用(如果是基本數(shù)據(jù)類型改备,檢查它們是否是相同的值)控漠。在實(shí)現(xiàn)了equals方法之后,通常會(huì)使用這個(gè)運(yùn)算符來優(yōu)化調(diào)用代碼悬钳。注意盐捷,===運(yùn)算符不能被重載。

equals函數(shù)之所以被標(biāo)記為override他去,那是因?yàn)榕c其他約定不同的是毙驯,這個(gè)方法的實(shí)現(xiàn)是在Any類中定義的(Kotlin中的所有對(duì)象都支持等式比較),Any中的基本方法就已經(jīng)標(biāo)記了operator灾测,并且函數(shù)的operator修飾符適用于所有實(shí)現(xiàn)或重寫它的方法爆价。

另外equals不能實(shí)現(xiàn)為擴(kuò)展函數(shù),因?yàn)槔^承自Any類的實(shí)現(xiàn)始終優(yōu)先于擴(kuò)展函數(shù)媳搪。

排序運(yùn)算符:compareTo

Kotlin中比較運(yùn)算符(<铭段、>、<=秦爆、>=)的使用將被轉(zhuǎn)換為compareTo序愚,compareTo的返回類型必須為Int。

a >= b -> a.compareTo(b) >= 0
data class Point(val x: Int, val y: Int): Comparable<Point> {
    override fun compareTo(other: Point): Int {
        return compareValuesBy(this, other, Point::x, Point::y) //按順序調(diào)用給定的方法等限,并比較它們的值
    }
}

fun method() {
    val p1 = Point(100, 200)
    val p2 = Point(300, 100)
    val p3 = Point(50, 500)
    Log.d("TAG", "zwm, ${p1 > p2} ${p1 > p3}")
}

日志打影炙薄:
2020-08-06 16:50:28.966 10439-10439/com.tomorrow.kotlindemo D/TAG: zwm, false true

與equals一樣,operator修飾符已經(jīng)被用在了基類的接口中望门,因此在重寫該接口時(shí)無須再重復(fù)形娇。

3.集合與區(qū)間的約定

通過下標(biāo)來訪問元素:get和set

在Kotlin中,下標(biāo)運(yùn)算符是一個(gè)約定筹误,使用下標(biāo)運(yùn)算符獲取元素會(huì)被轉(zhuǎn)換為get運(yùn)算符方法的調(diào)用桐早,并且寫入元素將調(diào)用set。

實(shí)現(xiàn)get約定:

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

operator fun Point.get(index: Int): Int {
    return when(index) {
        0 -> x
        1 -> y
        else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
    }
}

fun method() {
    val p = Point(100, 200)
    Log.d("TAG", "zwm, ${p[0]} ${p[1]}")
}

日志打印:
2020-08-07 08:12:42.490 15636-15636/com.tomorrow.kotlindemo D/TAG: zwm, 100 200

注意哄酝,get的參數(shù)可以是任何類型友存,而不只是Int。例如陶衅,當(dāng)你對(duì)map使用下標(biāo)運(yùn)算符時(shí)屡立,參數(shù)類型是鍵的類型,它可以是任意類型搀军。還可以定義具有多個(gè)參數(shù)的get方法侠驯。如果需要使用不同的鍵類型訪問集合,也可以使用不同的參數(shù)類型定義多個(gè)重載的get方法奕巍。

實(shí)現(xiàn)set約定:

data class MutablePoint(var x: Int, var y: Int)

operator fun MutablePoint.set(index: Int, value: Int) {
    return when(index) {
        0 -> x = value
        1 -> y = value
        else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
    }
}

fun method() {
    val p = MutablePoint(100, 200)
    p[0] = 50
    p[1] = 60
    Log.d("TAG", "zwm, $p")
}

日志打右鞑摺:
2020-08-07 08:20:02.863 16265-16265/com.tomorrow.kotlindemo D/TAG: zwm, MutablePoint(x=50, y=60)

in的約定

in運(yùn)算符用于檢查某個(gè)對(duì)象是否屬于集合,相應(yīng)的函數(shù)叫作contains的止,in右邊的對(duì)象將會(huì)調(diào)用contains函數(shù)檩坚,in左邊的對(duì)象將會(huì)作為函數(shù)入?yún)ⅲ?/p>

data class Point(val x: Int, val y: Int)
data class Rectangle(val upperLeft: Point, val lowerRight: Point)

operator fun Rectangle.contains(p: Point): Boolean {
    return p.x in upperLeft.x until lowerRight.x &&
            p.y in upperLeft.y until  lowerRight.y
}

fun method() {
    val rect = Rectangle(Point(10, 20), Point(50, 50))
    val p = Point(20, 30)
    val result = p in rect
    Log.d("TAG", "zwm, result: $result")
}

日志打印:
2020-08-07 08:33:33.966 17526-17526/? D/TAG: zwm, result: true

標(biāo)準(zhǔn)庫(kù)的until函數(shù)用于構(gòu)建一個(gè)開區(qū)間诅福,開區(qū)間是不包括最后一個(gè)點(diǎn)的區(qū)間匾委。例如,如果用10..20構(gòu)建一個(gè)普通的區(qū)間(閉區(qū)間)氓润,該區(qū)間則包括10到20的所有數(shù)字赂乐,包括20。開區(qū)間10 until 20包括10到19的數(shù)字咖气,但不包括20挨措。

rangeTo的約定

..運(yùn)算符是調(diào)用rangeTo函數(shù)的一個(gè)簡(jiǎn)潔方法。rangeTo函數(shù)返回一個(gè)區(qū)間崩溪,你可以為自己的類定義這個(gè)運(yùn)算符痹筛。但是挺尾,如果該類實(shí)現(xiàn)了Comparable接口荞彼,那么不需要了世杀;你可以通過Kotlin標(biāo)準(zhǔn)庫(kù)創(chuàng)建一個(gè)任意可比較元素的區(qū)間,這個(gè)庫(kù)定義了可以用于任何可比較元素的rangeTo函數(shù):

operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
data class Point(val x: Int, val y: Int): Comparable<Point> {
    override fun compareTo(other: Point): Int {
        return compareValuesBy(this, other, Point::x, Point::y) //按順序調(diào)用給定的方法乳幸,并比較它們的值
    }
}

fun method() {
    val p = Point(20, 30)..Point(200, 300)
    val result = Point(100, 200) in p
    Log.d("TAG", "zwm, result: $result")
}

日志打拥伤稀:
2020-08-07 08:50:12.484 17878-17878/com.tomorrow.kotlindemo D/TAG: zwm, result: true

rangeTo運(yùn)算符的優(yōu)先級(jí)低于算術(shù)運(yùn)算符,但是最好把參數(shù)括起來以免混淆:

fun method() {
    val n = 9
    Log.d("TAG", "zwm, ${5..(n+1)}")
}

日志打哟舛稀:
2020-08-07 08:59:06.502 18325-18325/? D/TAG: zwm, 5..10

使用forEach:

fun method() {
    val n = 3
    (0..n).forEach {  Log.d("TAG", "zwm, $it") }
}

日志打臃贰:
2020-08-07 09:01:14.898 18611-18611/com.tomorrow.kotlindemo D/TAG: zwm, 0
2020-08-07 09:01:14.898 18611-18611/com.tomorrow.kotlindemo D/TAG: zwm, 1
2020-08-07 09:01:14.898 18611-18611/com.tomorrow.kotlindemo D/TAG: zwm, 2
2020-08-07 09:01:14.898 18611-18611/com.tomorrow.kotlindemo D/TAG: zwm, 3

在for循環(huán)中使用iterator的約定

在Kotlin中,for循環(huán)也可以使用in運(yùn)算符姿染,和做區(qū)間檢查一樣背亥。但是在這種情況下它的含義是不同的:它被用來執(zhí)行迭代。這意味著一個(gè)諸如for(x in list) {...} 將被轉(zhuǎn)換成list.iterator()的調(diào)用悬赏,然后就像在Java中一樣狡汉,在它上面重復(fù)調(diào)用hasNext和next方法:

fun method() {
    val list = listOf(Point(10, 20), Point(20, 30))
    for(item in list) {
        Log.d("TAG", "zwm, $item")
    }
}

日志打印:
2020-08-07 09:10:59.935 19483-19483/com.tomorrow.kotlindemo D/TAG: zwm, Point(x=10, y=20)
2020-08-07 09:10:59.935 19483-19483/com.tomorrow.kotlindemo D/TAG: zwm, Point(x=20, y=30)

注意闽颇,在Kotlin中盾戴,這也是一種約定,這意味著iterator方法可以被定義為擴(kuò)展函數(shù)兵多,這就解釋了為什么可以遍歷一個(gè)常規(guī)的Java字符串:標(biāo)準(zhǔn)庫(kù)已經(jīng)為CharSequence定義了一個(gè)擴(kuò)展函數(shù)iterator尖啡,而它是String的父類:

operator fun CharSequence.iterator(): CharIterator //這個(gè)庫(kù)函數(shù)讓迭代字符串成為可能
fun method() {
    val str = "abc"
    for(c in str) {
        Log.d("TAG", "zwm, $c")
    }
}

日志打印:
2020-08-07 09:16:30.770 19952-19952/? D/TAG: zwm, a
2020-08-07 09:16:30.770 19952-19952/? D/TAG: zwm, b
2020-08-07 09:16:30.771 19952-19952/? D/TAG: zwm, c

可以為自己的類定義iterator方法:

data class Point(val x: Int): Comparable<Point> {
    override fun compareTo(other: Point): Int {
        return compareValuesBy(this, other, Point::x)
    }
}

operator fun ClosedRange<Point>.iterator(): Iterator<Point> =
    object : Iterator<Point> {
        var current = start

        override fun hasNext() = current <= endInclusive

        override fun next() = current.apply {
           current = Point(this.x + 1)
        }
    }

fun method() {
    val p = Point(5)..Point(8)
    for(i in p) {
        Log.d("TAG", "zwm, result: $i")
    }
}

日志打邮1臁:
2020-08-07 09:42:07.980 21694-21694/? D/TAG: zwm, result: Point(x=5)
2020-08-07 09:42:07.980 21694-21694/? D/TAG: zwm, result: Point(x=6)
2020-08-07 09:42:07.980 21694-21694/? D/TAG: zwm, result: Point(x=7)
2020-08-07 09:42:07.980 21694-21694/? D/TAG: zwm, result: Point(x=8)

4.解構(gòu)聲明和組件函數(shù)

解構(gòu)聲明功能允許你展開單個(gè)復(fù)合值衅斩,并使用它來初始化多個(gè)單獨(dú)的變量:

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

fun method() {
    val p = Point(100, 200)
    val (x, y) = p
    Log.d("TAG", "zwm, $x $y")
}

日志打印:
2020-08-07 09:53:25.024 23435-23435/com.tomorrow.kotlindemo D/TAG: zwm, 100 200

一個(gè)解構(gòu)聲明看起來像一個(gè)普通的變量聲明怠褐,但它在括號(hào)中有多個(gè)變量畏梆。事實(shí)上,解構(gòu)聲明再次用到了約定的原理奈懒。要在解構(gòu)聲明中初始化每個(gè)變量奠涌,將調(diào)用名為componentN的函數(shù),其中N是聲明中變量的位置磷杏。

對(duì)于數(shù)據(jù)類溜畅,編譯器為每個(gè)在主構(gòu)造方法中聲明的屬性生成一個(gè)componentN函數(shù)。而對(duì)于非數(shù)據(jù)類极祸,則需要手動(dòng)進(jìn)行聲明:

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

fun method() {
    val p = Point(100, 200)
    val (x, y) = p
    Log.d("TAG", "zwm, $x $y")
}

日志打哟雀瘛:
2020-08-07 10:01:43.264 24067-24067/? D/TAG: zwm, 100 200

使用解構(gòu)聲明來返回多個(gè)值:

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

fun getPoint(arr: Array<Int>): Point {
    return Point(arr[0], arr[1])
}

fun method() {
    val arr = arrayOf(100, 200)
    val (x, y) = getPoint(arr)
    Log.d("TAG", "zwm, $x $y")
}

日志打印:
2020-08-07 10:13:35.270 25173-25173/com.tomorrow.kotlindemo D/TAG: zwm, 100 200

使用解構(gòu)聲明來處理集合:

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

fun getPoint(str: String): Point {
    val (x, y) = str.split(',', limit = 2)
    return Point(x.toInt(), y.toInt())
}

fun method() {
    val arr = "100,200"
    val (x, y) = getPoint(arr)
    Log.d("TAG", "zwm, $x $y")
}

日志打右=稹:
2020-08-07 10:19:10.701 25706-25706/com.tomorrow.kotlindemo D/TAG: zwm, 100 200

用解構(gòu)聲明來遍歷map:

fun method() {
    val map = mapOf( 1 to "one", 2 to "two", 3 to "three")
    for((key, value) in map) {
        Log.d("TAG", "zwm, $key $value")
    }
}

日志打勇鸵:
2020-08-07 10:24:04.482 26149-26149/com.tomorrow.kotlindemo D/TAG: zwm, 1 one
2020-08-07 10:24:04.482 26149-26149/com.tomorrow.kotlindemo D/TAG: zwm, 2 two
2020-08-07 10:24:04.482 26149-26149/com.tomorrow.kotlindemo D/TAG: zwm, 3 three

5.重用屬性訪問的邏輯:委托屬性

委托屬性的基本操作

委托屬性的基本語法是這樣的:

class Foo {
    var p: Type by Delegate()
}

屬性p將它的訪問器邏輯委托給了另一個(gè)對(duì)象:這里是Delegate類的一個(gè)新的實(shí)例。通過關(guān)鍵字by對(duì)其后的表達(dá)式求值來獲取這個(gè)對(duì)象汰规,關(guān)鍵字by可以用于任何符合屬性委托約定規(guī)則的對(duì)象汤功。編譯器生成的代碼如下:

class Foo {
    private val delegate = Delegate() //編譯器會(huì)自動(dòng)生成一個(gè)輔助屬性
    val p: Type
        set(value: Type) = delegate.setValue(..., value)
        get() = delegate.getValue(..)
}

例子:

class Foo {
    var city : String by Delegate()
}

class Delegate {
    operator fun getValue(foo: Foo, property: KProperty<*>): String {
        Log.d("TAG", "zwm, Delegate getValue, property name: ${property.name}")
        return "getValue"
    }

    operator fun setValue(foo: Foo, property: KProperty<*>, s: String) {
        Log.d("TAG", "zwm, Delegate setValue, property name: ${property.name}, value: $s")
    }
}

fun method() {
    val foo = Foo()
    Log.d("TAG", "zwm, foo.city 1: ${foo.city}")
    Log.d("TAG", "zwm, foo.city 2: ${foo.city}")
    foo.city = "guangzhou"
    Log.d("TAG", "zwm, foo.city 3: ${foo.city}")
}

日志打印:
2020-08-07 14:14:53.351 8868-8868/com.tomorrow.kotlindemo D/TAG: zwm, Delegate getValue, property name: city
2020-08-07 14:14:53.351 8868-8868/com.tomorrow.kotlindemo D/TAG: zwm, foo.city 1: getValue
2020-08-07 14:14:53.351 8868-8868/com.tomorrow.kotlindemo D/TAG: zwm, Delegate getValue, property name: city
2020-08-07 14:14:53.351 8868-8868/com.tomorrow.kotlindemo D/TAG: zwm, foo.city 2: getValue
2020-08-07 14:14:53.352 8868-8868/com.tomorrow.kotlindemo D/TAG: zwm, Delegate setValue, property name: city, value: guangzhou
2020-08-07 14:14:53.352 8868-8868/com.tomorrow.kotlindemo D/TAG: zwm, Delegate getValue, property name: city
2020-08-07 14:14:53.352 8868-8868/com.tomorrow.kotlindemo D/TAG: zwm, foo.city 3: getValue

使用委托屬性:惰性初始化和by lazy()

惰性初始化是一種常見的模式溜哮,直到第一次訪問該屬性的時(shí)候滔金,才根據(jù)需要?jiǎng)?chuàng)建對(duì)象的一部分。當(dāng)初始化過程消耗大量資源并且在使用對(duì)象時(shí)并不總是需要數(shù)據(jù)時(shí)茂嗓,這個(gè)非常有用餐茵。使用委托屬性會(huì)讓代碼變得簡(jiǎn)單得多,可以封裝用于存儲(chǔ)值的支持屬性和確保該值只被初始化一次的邏輯述吸。在這里可以使用標(biāo)準(zhǔn)庫(kù)函數(shù)lazy返回的委托忿族,lazy函數(shù)返回一個(gè)對(duì)象锣笨,該對(duì)象具有一名為getValue且簽名正確的方法,因此可以把它與by關(guān)鍵字一起使用來創(chuàng)建一個(gè)委托屬性道批。lazy的參數(shù)是一個(gè)lambda错英,可以調(diào)用它來初始化這個(gè)值。

例子:

class Foo {
    val city: String by lazy {
        Log.d("TAG", "zwm, lazy")
        "lazy"
    }
}

fun method() {
    val foo = Foo()
    Log.d("TAG", "zwm, foo.city 1: ${foo.city}")
    Log.d("TAG", "zwm, foo.city 2: ${foo.city}")
}

日志打勇”:
2020-08-07 14:22:50.565 9447-9447/com.tomorrow.kotlindemo D/TAG: zwm, lazy
2020-08-07 14:22:50.565 9447-9447/com.tomorrow.kotlindemo D/TAG: zwm, foo.city 1: lazy
2020-08-07 14:22:50.566 9447-9447/com.tomorrow.kotlindemo D/TAG: zwm, foo.city 2: lazy

by lazy語法的特點(diǎn)如下:

  • 該變量必須是引用不可變的椭岩,而不能通過var來聲明。
  • 在被首次調(diào)用時(shí)璃赡,才會(huì)進(jìn)行賦值操作判哥。一旦被賦值,后續(xù)它將不能被更改碉考。

lazy的背后是接受一個(gè)lambda并返回一個(gè)Lazy<T>實(shí)例的函數(shù)塌计,第一次訪問該屬性時(shí),會(huì)執(zhí)行l(wèi)azy對(duì)應(yīng)的Lambda表達(dá)式并記錄結(jié)果侯谁,后續(xù)訪問該屬性時(shí)只是返回記錄的結(jié)果夺荒。另外系統(tǒng)會(huì)給lazy屬性默認(rèn)加上同步鎖,也就是LazyThreadSafetyMode.SYNCHRONIZED良蒸,它在同一時(shí)刻只允許一個(gè)線程對(duì)lazy屬性進(jìn)行初始化技扼,所以它是線程安全的。但若你能確認(rèn)該屬性可以并行執(zhí)行嫩痰,沒有線程安全問題剿吻,那么可以給lazy傳遞LazyThreadSafetyMode.PUBLICATION參數(shù)。你還可以給lazy傳遞LazyThreadSafetyMode.NONE參數(shù)串纺,這將不會(huì)有任何線程方面的開銷丽旅,當(dāng)然也不會(huì)有任何線程安全的保證。比如:

class Person(val color: String) {
    val sex: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
        //并行模式
        if(color == "yellow") "male" else "female"
    }
}

class Person(val color: String) {
    val sex: String by lazy(LazyThreadSafetyMode.NONE) {
        //不做任何線程保證也不會(huì)有任何線程開銷
        if(color == "yellow") "male" else "female"
    }
}

Delegates.observable函數(shù)可以用來添加屬性更改的觀察者:

class Foo {
    var city: String by Delegates.observable("init") {
        prop, old, new ->
        Log.d("TAG", "zwm, observable: ${prop.name} $old $new")
    }
}

fun method() {
    val foo = Foo()
    Log.d("TAG", "zwm, foo.city: ${foo.city}")
    foo.city = "guangzhou"
}

日志打臃墓住:
2020-08-07 14:28:44.590 10176-10176/com.tomorrow.kotlindemo D/TAG: zwm, foo.city: init
2020-08-07 14:28:44.591 10176-10176/com.tomorrow.kotlindemo D/TAG: zwm, observable: city init guangzhou

委托屬性可以使用任意map來作為屬性委托榄笙,來靈活處理具有可變屬性集的對(duì)象:

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

fun method() {
    val map = mutableMapOf("name" to "Tomy", "city" to "gz")
    val site = Site(map)
    Log.d("TAG", "zwm, name: ${site.name} url: ${site.city}")
    map["name"] = "Zhang"
    Log.d("TAG", "zwm, name: ${site.name} url: ${site.city}")
}

日志打印:
2020-08-07 14:49:31.047 11820-11820/com.tomorrow.kotlindemo D/TAG: zwm, name: Tomy url: gz
2020-08-07 14:49:31.047 11820-11820/com.tomorrow.kotlindemo D/TAG: zwm, name: Zhang url: gz

八祷蝌、高階函數(shù):Lambda作為形參和返回值

1.聲明高階函數(shù)

高階函數(shù)就是以另一個(gè)函數(shù)作為參數(shù)或者返回值的函數(shù)茅撞。在Kotlin中,函數(shù)可以用lambda或者函數(shù)引用來表示巨朦。

Kotlin中的函數(shù)類型語法:

(int, String) -> Unit //格式:(參數(shù)類型) -> 返回類型米丘,如果沒有參數(shù)類型就用()來表示

例如:

val sum = { x: Int, y:Int -> x + y }
val sum: (Int, Int) -> Int = { x, y -> x + y }

調(diào)用作為參數(shù)的函數(shù):

fun twoAndThree(operation: (Int, Int) -> Int) { //定義一個(gè)函數(shù)類型的參數(shù)
    val result = operation(2, 3)
    Log.d("TAG", "zwm, result: $result")
}

fun method() {
    twoAndThree { x, y -> x + y }
}

日志打印:
2020-08-12 08:55:52.236 21495-21495/com.tomorrow.kotlindemo D/TAG: zwm, result: 5

在Java中可以很簡(jiǎn)單地調(diào)用使用了函數(shù)類型的Kotlin函數(shù)糊啡。Java 8的lambda會(huì)被自動(dòng)轉(zhuǎn)換為函數(shù)類型的值:

//shapes.kt
fun twoAndThree(operation: (Int, Int) -> Int): Int {
    val result = operation(2, 3)
    Log.d("TAG", "zwm, result: $result")
    return result
}

//JavaDemo.java
int result = ShapesKt.twoAndThree(x, y -> x + y);

在舊版的Java中(Java 8以前)拄查,可以傳遞一個(gè)實(shí)現(xiàn)了函數(shù)接口中的invoke方法的匿名類的實(shí)例:

//shapes.kt
package com.tomorrow.kotlindemo.example

import android.util.Log

fun twoAndThree(operation: (Int, Int) -> Int): Int { //定義一個(gè)函數(shù)類型的參數(shù)
    val result = operation(2, 3)
    Log.d("TAG", "zwm, result: $result")
    return result
}

//JavaDemo.java
package com.tomorrow.kotlindemo;

import android.util.Log;

import com.tomorrow.kotlindemo.example.ShapesKt;

import kotlin.jvm.functions.Function2;

public class JavaDemo {

    public void testDemo() {
        int result = ShapesKt.twoAndThree(new Function2<Integer, Integer, Integer>() {
            @Override
            public Integer invoke(Integer integer, Integer integer2) {
                return integer + integer2;
            }
        });
        Log.d("TAG", "zwm, java result: " + result);
    }
}

日志打印:
2020-08-12 09:22:31.998 24355-24355/com.tomorrow.kotlindemo D/TAG: zwm, result: 5
2020-08-12 09:22:31.998 24355-24355/com.tomorrow.kotlindemo D/TAG: zwm, java result: 5

函數(shù)類型的參數(shù)默認(rèn)值和null值:

fun twoAndThree(operation: (Int, Int) -> Int = { x, y -> x * y }): Int { //定義一個(gè)函數(shù)類型的參數(shù)
    val result = operation(2, 3)
    Log.d("TAG", "zwm, result: $result")
    return result
}

fun method() {
    twoAndThree()
}

日志打优镄睢:
2020-08-12 09:28:53.663 25496-25496/com.tomorrow.kotlindemo D/TAG: zwm, result: 6
fun twoAndThree(operation: ((Int, Int) -> Int)? = null): Int {
    if(operation != null) { //顯式判空
        val result = operation(2, 3)
        Log.d("TAG", "zwm, result: $result")
        return result
    }
    return -1
}

fun method() {
    twoAndThree()
}
fun twoAndThree(operation: ((Int, Int) -> Int)? = null): Int {
    val result = operation?.invoke(2, 3) ?: (2 + 3) //函數(shù)類型是一個(gè)包含invoke方法的接口的具體實(shí)現(xiàn)
    Log.d("TAG", "zwm, result: $result")
    return result
}

fun method() {
    twoAndThree()
}

日志打佣榉觥:
2020-08-12 09:35:00.001 25823-25823/com.tomorrow.kotlindemo D/TAG: zwm, result: 5

返回函數(shù)的函數(shù):

fun getAddCalculator(): (Int, Int) -> Int {
    return { x, y -> x + y }
}

fun method() {
    val calculator = getAddCalculator()
    val result = calculator(2, 3)
    Log.d("TAG", "zwm, result: $result")
}

日志打影唷:
2020-08-12 09:45:19.782 26722-26722/com.tomorrow.kotlindemo D/TAG: zwm, result: 5

2.內(nèi)聯(lián)函數(shù):消除lambda帶來的運(yùn)行時(shí)開銷

Lambda表達(dá)式會(huì)被正常地編譯成匿名類,這表示每調(diào)用一次lambda表達(dá)式稍算,一個(gè)額外的類就會(huì)被創(chuàng)建典尾。并且如果lambda捕捉了某個(gè)變量,那么每次調(diào)用的時(shí)候都會(huì)創(chuàng)建一個(gè)新的對(duì)象邪蛔。這會(huì)帶來運(yùn)行時(shí)的額外開銷,導(dǎo)致使用lambda比使用一個(gè)直接執(zhí)行相同代碼的函數(shù)效率更低扎狱。

如果使用inline修飾符標(biāo)記一個(gè)函數(shù)侧到,在函數(shù)被使用的時(shí)候編譯器并不會(huì)生成函數(shù)調(diào)用的代碼,而是使用函數(shù)實(shí)現(xiàn)的真實(shí)代碼替換每一次的函數(shù)調(diào)用:

inline fun twoAndThree(operator: (Int, Int) -> Int) { //內(nèi)聯(lián)函數(shù)定義
    val result = operator(2, 3)
    Log.d("TAG", "zwm, result: $result")
}

fun method() {
    Log.d("TAG", "zwm, before inline")
    twoAndThree{ x, y -> x + y } //內(nèi)聯(lián)函數(shù)調(diào)用
    Log.d("TAG", "zwm, after inline")
}

不是所有使用lambda的函數(shù)都可以被內(nèi)聯(lián)淤击。當(dāng)函數(shù)被內(nèi)聯(lián)的時(shí)候匠抗,作為參數(shù)的lambda表達(dá)式的函數(shù)體會(huì)被直接替換到最終生成的代碼中。這將限制函數(shù)體中的對(duì)應(yīng)lambda參數(shù)的使用污抬。如果lambda參數(shù)被調(diào)用汞贸,這樣的代碼能被容易地內(nèi)聯(lián)。但如果lambda參數(shù)在某個(gè)地方被保存起來印机,以便后面可以繼續(xù)使用矢腻,lambda表達(dá)式的代碼將不能被內(nèi)聯(lián),因?yàn)楸仨氁幸粋€(gè)包含這些代碼的對(duì)象存在:

inline fun twoAndThree(operator: (Int, Int) -> Int) { //內(nèi)聯(lián)函數(shù)定義
    val result = operator(2, 3)
    Log.d("TAG", "zwm, result: $result")
}

fun callInlineMethod(param: (Int, Int) -> Int) {
    Log.d("TAG", "zwm, before call inline")
    twoAndThree(param) //在調(diào)用的地方還沒有l(wèi)ambda射赛,因此沒有被內(nèi)聯(lián)
    Log.d("TAG", "zwm, after call inline")
}

如果一個(gè)函數(shù)期望兩個(gè)或更多l(xiāng)ambda參數(shù)多柑,可以選擇只內(nèi)聯(lián)其中一些參數(shù)。因?yàn)橐粋€(gè)lambda可能會(huì)包含很多代碼或者以不允許內(nèi)聯(lián)的方式使用:

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
    //...
}

注意楣责,編譯器完全支持內(nèi)聯(lián)跨模塊的函數(shù)或者第三方庫(kù)定義的函數(shù)竣灌。也可以在Java中調(diào)用絕大部分內(nèi)聯(lián)函數(shù),但這些調(diào)用并不會(huì)被內(nèi)聯(lián)秆麸,而是編譯成普通的函數(shù)調(diào)用初嘹。

內(nèi)聯(lián)集合操作:

filter和map函數(shù)都被聲明為inline函數(shù),所以它們的函數(shù)體會(huì)被內(nèi)聯(lián)沮趣,因此不會(huì)產(chǎn)生額外的類或者對(duì)象屯烦。如果有大量元素需要處理,中間集合的運(yùn)行開銷將成為不可忽視的問題房铭,這時(shí)可以在調(diào)用鏈后加一個(gè)asSequence調(diào)用漫贞,用序列來替代集合。但是用來處理序列的lambda沒有被內(nèi)聯(lián)育叁。每一個(gè)中間序列被表示成把lambda保存在其字段中的對(duì)象迅脐,而末端操作會(huì)導(dǎo)致由每一個(gè)中間序列調(diào)用組成的調(diào)用鏈被執(zhí)行。因此豪嗽,即便序列上的操作是惰性的谴蔑,你不應(yīng)該總是試圖在集合操作的調(diào)用鏈后加上asSequence豌骏。這只在處理大量數(shù)據(jù)的集合時(shí)有用,小的集合可以用普通的集合操作處理隐锭。

決定何時(shí)將函數(shù)聲明成內(nèi)聯(lián):

使用inline關(guān)鍵字只能提高帶有l(wèi)ambda參數(shù)的函數(shù)的性能窃躲,其他的情況需要額外的度量和研究。對(duì)于普通的函數(shù)調(diào)用钦睡,JVM已經(jīng)提供了強(qiáng)大的內(nèi)聯(lián)支持蒂窒。

Kotlin庫(kù)中withLock函數(shù)的定義:

fun <T> Lock.withLock(action: () -> T): T {
    lock()
    try {
        return action()
    } finally {
        unlock()
    }
}

Kotlin的withLock語句的使用:

fun method() {
    val lock: Lock = ReentrantLock()
    lock.withLock { Log.d("TAG", "zwm, with lock") }
}

日志打印:
2020-08-12 13:32:21.455 9622-9622/com.tomorrow.kotlindemo D/TAG: zwm, with lock

Java 7的try-with-resource語句的使用:

public String testDemo(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

Kotlin標(biāo)準(zhǔn)庫(kù)中有個(gè)use函數(shù)荞怒,是一個(gè)擴(kuò)展函數(shù)洒琢,被用來操作可關(guān)閉的資源,它接收一個(gè)lambda作為參數(shù)褐桌。這個(gè)方法調(diào)用lambda并且確保資源被關(guān)閉衰抑,無論lambda正常執(zhí)行還是拋出了異常。當(dāng)然荧嵌,use函數(shù)是內(nèi)聯(lián)函數(shù)呛踊,所以使用它并不會(huì)引發(fā)任何性能開銷:

fun method(path: String): String {
    BufferedReader(FileReader(path)).use { br -> return br.readLine() }
}

3.高階函數(shù)中的控制流

lambda中的返回語句:從一個(gè)封閉的函數(shù)返回

如果你在lambda中使用return關(guān)鍵字,它會(huì)從調(diào)用lambda的函數(shù)中返回啦撮,并不只是從lambda中返回谭网。這樣的return語句叫作非局部返回,因?yàn)樗鼜囊粋€(gè)比包含return的代碼塊更大的代碼塊中返回了赃春。

需要注意的是蜻底,只有在以lambda作為參數(shù)的函數(shù)是內(nèi)聯(lián)函數(shù)的時(shí)候才能從更外層的函數(shù)返回。forEach的函數(shù)體和lambda的函數(shù)體一起被內(nèi)聯(lián)了聘鳞,所以在編譯的時(shí)候能很容易做到從包含它的函數(shù)中返回薄辅。在一個(gè)非內(nèi)聯(lián)函數(shù)的lambda中使用return表達(dá)式是不允許的。

fun method() {
    val list = listOf(Person("Kotlin"), Person("Java"))
    list.forEach {
        Log.d("TAG", "zwm, name: ${it.name}")
        return
    }
    Log.d("TAG", "zwm, method end")
}

日志打涌倭А:
2020-08-12 14:04:43.195 14730-14730/com.tomorrow.kotlindemo D/TAG: zwm, name: Kotlin

從lambda返回:使用標(biāo)簽返回

想從一個(gè)lambda表達(dá)式處返回站楚, 你可以標(biāo)記它,然后在return關(guān)鍵字后面引用這標(biāo)簽搏嗡。要標(biāo)記一個(gè)lambda表達(dá)式窿春,在lambda的花括號(hào)之前放一個(gè)標(biāo)簽名(可以是任何標(biāo)識(shí)符),接著放一個(gè)@符號(hào)采盒。要從一個(gè)lambda返回旧乞,在return關(guān)鍵字后放一個(gè)@符號(hào),接著放標(biāo)簽名磅氨。

fun method() {
    val list = listOf(Person("Kotlin"), Person("Java"))
    list.forEach label@{
        Log.d("TAG", "zwm, name: ${it.name}")
        return@label
    }
    Log.d("TAG", "zwm, method end")
}

日志打映咂堋:
2020-08-12 14:26:21.882 16310-16310/? D/TAG: zwm, name: Kotlin
2020-08-12 14:26:21.883 16310-16310/? D/TAG: zwm, name: Java
2020-08-12 14:26:21.883 16310-16310/? D/TAG: zwm, method end

使用lambda作為參數(shù)的函數(shù)的函數(shù)名可以作為標(biāo)簽:

fun method() {
    val list = listOf(Person("Kotlin"), Person("Java"))
    list.forEach {
        Log.d("TAG", "zwm, name: ${it.name}")
        return@forEach
    }
    Log.d("TAG", "zwm, method end")
}

日志打印:
2020-08-12 14:28:12.182 16512-16512/com.tomorrow.kotlindemo D/TAG: zwm, name: Kotlin
2020-08-12 14:28:12.182 16512-16512/com.tomorrow.kotlindemo D/TAG: zwm, name: Java
2020-08-12 14:28:12.183 16512-16512/com.tomorrow.kotlindemo D/TAG: zwm, method end

如果你顯式地指定了lambda表達(dá)式的標(biāo)簽烦租,再使用函數(shù)名作為標(biāo)簽沒有任何效果延赌,一個(gè)lambda表達(dá)式的標(biāo)簽數(shù)量不能多于一個(gè)除盏。

帶標(biāo)簽的this表達(dá)式

如果你給帶接收者的lambda指定標(biāo)簽,就可以通過對(duì)應(yīng)的帶有標(biāo)簽的this表達(dá)式訪問它的隱式接收者:

fun method() {
    val result = StringBuilder().apply sb@{
        listOf(1, 2, 3).apply {
            this@sb.append(this.toString())
        }
    }
    Log.d("TAG", "zwm, result: ${result.toString()}")
}

日志打哟煲浴:
2020-08-12 14:44:00.654 17336-17336/com.tomorrow.kotlindemo D/TAG: zwm, result: [1, 2, 3]

匿名函數(shù):默認(rèn)使用局部返回

在匿名函數(shù)中使用return:

data class Person(val name: String)

fun method() {
    val list = listOf(Person("Kotlin"), Person("Java"))
    list.forEach(fun (person) {
        Log.d("TAG", "zwm, name: ${person.name}")
        return
    })
    Log.d("TAG", "zwm, method end")
}

日志打诱呷洹:
2020-08-12 14:53:45.399 18383-18383/? D/TAG: zwm, name: Kotlin
2020-08-12 14:53:45.399 18383-18383/? D/TAG: zwm, name: Java
2020-08-12 14:53:45.399 18383-18383/? D/TAG: zwm, method end

在filter中使用匿名函數(shù):

data class Person(val name: String)

fun method() {
    val list = listOf(Person("Kotlin"), Person("Java"))
    val result = list.filter(fun (person): Boolean {
        return person.name == "Kotlin"
    })
    Log.d("TAG", "zwm, method end: $result")
}

日志打印:
2020-08-12 14:56:31.894 18622-18622/? D/TAG: zwm, method end: [Person(name=Kotlin)]

使用表達(dá)式體匿名函數(shù):

data class Person(val name: String)

fun method() {
    val list = listOf(Person("Kotlin"), Person("Java"))
    val result = list.filter(fun (person) = person.name == "Kotlin" )
    Log.d("TAG", "zwm, method end: $result")
}

日志打悠伞:
2020-08-12 14:59:32.693 19035-19035/com.tomorrow.kotlindemo D/TAG: zwm, method end: [Person(name=Kotlin)]

在匿名函數(shù)中踱侣,不帶標(biāo)簽的return表達(dá)式會(huì)從匿名函數(shù)返回,而不是從包含匿名函數(shù)的函數(shù)返回大磺。這條規(guī)則很簡(jiǎn)單:return從最近的使用fun關(guān)鍵字聲明的函數(shù)返回抡句。lambda表達(dá)式?jīng)]有使用fun關(guān)鍵字,所以lambda中的return從最外層的函數(shù)返回量没。匿名函數(shù)使用了fun玉转,因此return表達(dá)式從匿名函數(shù)返回突想,而不是從最外層的函數(shù)返回殴蹄。盡管匿名函數(shù)看起來跟普通函數(shù)很相似,但它其實(shí)是lambda表達(dá)式的另一種語法形式而已猾担。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末袭灯,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子绑嘹,更是在濱河造成了極大的恐慌稽荧,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件工腋,死亡現(xiàn)場(chǎng)離奇詭異姨丈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)擅腰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門蟋恬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人趁冈,你說我怎么就攤上這事歼争。” “怎么了渗勘?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵沐绒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我旺坠,道長(zhǎng)茧球,這世上最難降的妖魔是什么咱筛? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮烤黍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘碘裕。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布巷蚪。 她就那樣靜靜地躺著,像睡著了一般濒翻。 火紅的嫁衣襯著肌膚如雪屁柏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天有送,我揣著相機(jī)與錄音淌喻,去河邊找鬼。 笑死雀摘,一個(gè)胖子當(dāng)著我的面吹牛裸删,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播阵赠,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼涯塔,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了清蚀?” 一聲冷哼從身側(cè)響起匕荸,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎枷邪,沒想到半個(gè)月后榛搔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡东揣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年践惑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘶卧。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡尔觉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出脸候,到底是詐尸還是另有隱情穷娱,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布运沦,位于F島的核電站泵额,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏携添。R本人自食惡果不足惜嫁盲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧羞秤,春花似錦缸托、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哺哼,卻和暖如春佩抹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背取董。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工棍苹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人茵汰。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓枢里,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蹂午。 傳聞我的和親對(duì)象是個(gè)殘疾皇子栏豺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359