Kotlin核心語法(四):kotlin Lambda編程

博客主頁java

1. Lambda表達式和成員引用

Lambda簡介:做爲函數參數的代碼塊

// lambda表達式顯現監聽器
button.setOnClickListener { /* ... */ }

Lambda 和 集合

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

>>>     val list = listOf(Person("kerwin", 12), Person("Bob", 23))
//      用lambda在集合中搜索,比較年齡找到最大的元素
>>>     // fun <T, R : Comparable<R>> Iterable<T>.maxBy(selector: (T) -> R): T?
//      接收一個集合中的元素做爲實參(使用it引用它)並返回用來比較的值,簡明寫法
//      若是隻有一個參數的lambda,且這個參數的類型能夠推導出來,會生成默認參數名稱it
>>>     println(list.maxBy { it.age })

//      花括號中的代碼片斷是lambda表達式,把它做爲實參傳給這個函數
//      這個lambda接收一個類型爲Person的參數並返回它的年齡
>>>     list.maxBy { person -> person.age }



//      若是lambda恰好是函數或者屬性的委託,能夠用成員引用替換
>>>     println(list.maxBy(Person::age))

Lambda表達式的語法

一個lambda把一段行爲進行編碼,能把它看成值處處傳遞,能夠被獨立的聲明並存儲到一個變量中。編程

// lambda表達式的語法
// 參數 -> 函數體
{ x: Int, y: Int -> x + y }

// kotlin 的lambda表達式始終用花括號({})包圍。
// 實參並無用括號括起來,箭頭把實參列表和lambda的函數體隔開

能夠把lambda表達式存儲在一個變量中,把這個變量看成普通函數對待segmentfault

// 使用變量存儲lambda,不能推導出參數類型,必須顯式的指定參數類型
 val sum = { x: Int, y: Int -> x + y }
 println(sum(1, 2)) // 調用保存在變量中的lambda

kotlin中若是lambda表達式是函數調用的最後一個實參,它能夠放到括號的外邊app

list.maxBy() { person: Person ->
        person.age
    }

// 當lambda是函數惟一的實參時,能夠去掉調用代碼中的空括號
// 顯式的寫出參數類型
list.maxBy { person: Person ->
        person.age
    }

// 能夠不寫參數類型,會根據上下文推導出參數類型
list.maxBy { person -> person.age }

使用命名實參來傳遞lambdaide

val list = listOf(Person("kerwin", 12), Person("Bob", 23))
    // 把lambda做爲命名實參傳遞
    val names = list.joinToString(separator = " ", transform = { person: Person ->
        person.name
    })
    println(names) // kerwin Bob


    // 能夠把lambda放在括號外傳遞
    val names = list.joinToString(separator = " ") { person: Person ->
        person.name
    }

在做用域中訪問變量

在函數內部使用lambda,也能夠訪問這個函數的參數,還能夠在lambda以前定義局部變量函數

// 在lambda中使用函數參數
fun printMessageWithPrefix(messages: Collection<String>, prefix: String) {
    messages.forEach {
        // lambda中訪問prefix參數
        println("$prefix $it")
    }
}

val messages = listOf("404", "403")
printMessageWithPrefix(messages, "error: ")

kotlin容許lambda內部訪問非final變量甚至修改它們,從lambda內訪問外部變量,稱這些變量被lambda捕捉。post

成員引用

// 成員引用語法,使用::運算符
// 類::成員
Person::age

:: 運算符 能夠把函數轉換成一個值,如:val getAge = Person::age ,這種表達式稱爲成員引用ui

還能夠引用頂層函數(不是類的成員)this

fun test() = println("test")

// 引用頂層函數,省略了類的名稱,直接 ::開頭
// 成員引用 ::test被看成實參傳遞給庫函數,它會調用相應的函數
run(::test)

若是lambda要委託給一個接收多個參數的函數,成員引用代替會很是方便編碼

// 這個lambda委託給sendEmail函數
    val action = { person: Person, message: String ->
        sendEmail(person, message)
    }

    // 可使用成員引用代替
    val nextAction= ::sendEmail

 >>> action(Person("kerwin",12), "吃飯")
 >>> nextAction(Person("bob",34), "吃飯")

能夠用 構造方法引用 存儲或者延期執行建立類實例的動做。構造方法引用的形式是在雙冒號(::)後指定類名稱

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

    // 構造方法引用,建立Person實例的動做被保存成了值
    val createPerson = ::Person
    val person = createPerson("kerwin", 23)
    println(person)

還能夠引用擴展函數

// Person類擴展函數
fun Person.isAdult() = this.age >= 21

val person = Person("kerwin", 22)
// 雖然isAdult不是Person類的成員,但還能夠經過引用訪問它
val predicate = Person::isAdult
println(predicate(person))

2. 集合的函數式API

基礎:filter 和 map

filter 函數底層源碼:

public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    return filterTo(ArrayList<T>(), predicate)
}

public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
    for (element in this) if (predicate(element)) destination.add(element)
    return destination
}

filter 函數源碼可知:filter函數遍歷集合並選出知足給定lambda後返回true的元素,這些知足條件的元素存放在新的集合中。

val list = listOf(1, 2, 3, 4)
// 過濾出偶數元素
println(list.filter { it % 2 == 0 }) // [2, 4]

val list = listOf(Person("kerwin", 23), Person("Bob", 12))
// 過濾出年齡超過18的人
println(list.filter { it.age >= 18 }) // [Person(name=kerwin, age=23)]

map 函數底層源碼:

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C {
    for (item in this)
        destination.add(transform(item))
    return destination
}

map 函數源碼可知:map函數對集合中的每個元素應用給定的lambda後並把結果存儲在一個新的集合中。新集合中元素個數不變,但每一個元素根據給定的lambda作了變換。

val list = listOf(1, 2, 3, 4)
println(list.map { it * it }) // [1, 4, 9, 16]

val list = listOf(Person("kerwin", 23), Person("Bob", 12))
// 只須要姓名列表,可使用map轉換
println(list.map { it.name }) // kerwin, Bob]
// 可使用成員引用
list.map(Person::name)

// filter 和 map 能夠組合使用
// 輸出年齡超過18歲的姓名
println(list.filter { it.age >= 18 }.map { it.name }) // [kerwin]

// 找出年齡最大的人思路:先在集合中找到最大年齡,而後過濾最大年齡的人
val maxAge = list.maxBy(Person::age)?.age
println(list.filter { it.age == maxAge })

還能夠對Map集合應用過濾和變換函數:

val map = mapOf(1 to "one", 2 to "two")
println(map.mapValues { it.value.toUpperCase() }) // {1=ONE, 2=TWO}

// 鍵和值分別由各自的函數來處理。
// filterKeys 和 mapKeys 過濾和變換 Map的鍵
// filterValues 和 mapValues 過濾和變換 Map的值

all 、any、 count、 find :對集合應用判斷式

allany 函數檢查集合中的全部元素是否都符合某個條件(或者它的變種,是否存在符合的元素);
count 函數檢查有多少元素知足判斷式;
find 函數返回第一個符合條件的元素。

all函數底層源碼:

/**
 * Returns `true` if all elements match the given [predicate].
 */
public inline fun <T> Iterable<T>.all(predicate: (T) -> Boolean): Boolean {
    if (this is Collection && isEmpty()) return true
    for (element in this) if (!predicate(element)) return false
    return true
}

all 函數源碼可知:集合中的全部元素都知足條件才返回true,不然返回false。

val list = listOf(Person("kerwin", 12), Person("bob", 19))

    val result = list.all { person: Person ->
        person.age >= 18
    }

println(result)
false

any 函數底層源碼:

public inline fun <T> Iterable<T>.any(predicate: (T) -> Boolean): Boolean {
    if (this is Collection && isEmpty()) return false
    for (element in this) if (predicate(element)) return true
    return false
}

any 函數可知:集合中只要有一個元素知足條件就返回true,不然返回false

val list = listOf(Person("kerwin", 12), Person("bob", 19))

    val result = list.any { person: Person ->
        person.age >= 18
    }

println(result)
true

count 函數底層源碼:

public inline fun <T> Iterable<T>.count(predicate: (T) -> Boolean): Int {
    if (this is Collection && isEmpty()) return 0
    var count = 0
    for (element in this) if (predicate(element)) checkCountOverflow(++count)
    return count
}

count 函數可知:集合中元素知足條件的個數

val result = list.count { person: Person ->
        person.age >= 10
    }

println(result)
2

find 函數底層源碼:

public inline fun <T> Iterable<T>.find(predicate: (T) -> Boolean): T? {
    return firstOrNull(predicate)
}

public inline fun <T> Iterable<T>.firstOrNull(predicate: (T) -> Boolean): T? {
    for (element in this) if (predicate(element)) return element
    return null
}

find 函數可知:查找集合中第一個知足條件的元素,找到返回該元素,不然返回null

val result = list.find { person: Person ->
        person.age >= 18
    }

println(result)
Person(name=bob, age=19)

groupBy:把列表轉換成分組的map

groupBy 函數底層源碼:

public inline fun <T, K> Iterable<T>.groupBy(keySelector: (T) -> K): Map<K, List<T>> {
    return groupByTo(LinkedHashMap<K, MutableList<T>>(), keySelector)
}

public inline fun <T, K, M : MutableMap<in K, MutableList<T>>> Iterable<T>.groupByTo(destination: M, keySelector: (T) -> K): M {
    for (element in this) {
        val key = keySelector(element)
        val list = destination.getOrPut(key) { ArrayList<T>() }
        list.add(element)
    }
    return destination
}

public inline fun <K, V> MutableMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
    val value = get(key)
    return if (value == null) {
        val answer = defaultValue()
        put(key, answer)
        answer
    } else {
        value
    }
}

groupBy 函數可知:把集合中全部的元素按照不一樣的特性分紅不一樣的分組,返回Map集合,key:分組條件。value:List集合,每一個分組都存儲在一個列表中。

val list = listOf(
        Person("kerwin", 12),
        Person("bob", 19),
        Person("Alice", 12)
    )
 
    // 按照年齡分組,把相同年齡的人放在一組
    val groupList = list.groupBy { person: Person ->
        person.age
    }

println(groupList)
{12=[Person(name=kerwin, age=12), Person(name=Alice, age=12)], 19=[Person(name=bob, age=19)]}

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

flatMap 函數底層源碼:

public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
    return flatMapTo(ArrayList<R>(), transform)
}

public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
    for (element in this) {
        val list = transform(element)
        destination.addAll(list)
    }
    return destination
}

flatMap 函數可知:根據lambda給定的表達式(要返回的是Iterable子類)對每個元素作變換(或者說映射),而後把多個列表合併(或者說平鋪)成一個列表。

val books = listOf(
        Book("java", listOf("abc", "bcd")),
        Book("kotlin", listOf("wer"))
    )

    // 統計集合中每本書的做者合併一個扁平的列表
    val bookAllAuthors = books.flatMap { book: Book ->
        book.authors
    }

println(bookAllAuthors)
[abc, bcd, wer]

3. 惰性集合操做:序列

不少鏈式集合函數調用的時候,如:map 和 filter,這些函會及早的建立中間集合,也就是說每一步的中間結果都被存儲在一個臨時列表。序列 能夠避免建立這些臨時中間對象。

// 這種方式會建立臨時中間對象
    // 特色:先在每一個元素上調用map函數,而後在結果列表中的每一個元素上再調用filter函數
    list.map {
            println("map: $it")
            it.name
        }
        .filter {
            println("filter: $it")
            it.startsWith("k")
        }
        .toList()

    // 爲了提升效率,能夠把操做先變成序列,而不是直接使用集合 
    // 特色:全部操做是按照順序在每個元素上,處理完第一個元素(先映射再過濾),而後完成第二個元素的處理,以此類推
    list.asSequence()  // 把初始集合轉換成序列
        .map {
            println("map: $it")
            it.name
        } // 序列支持和集合同樣的API
        .filter {
            println("filter: $it")
            it.startsWith("k")
        }
        .toList()  // 把結果序列轉換回列表,反向轉換

kotlin惰性集合操做的入口就是:Sequence 接口,這個接口表示的就是一個能夠列舉元素的元素序列。它只提供了一個 方法iterator,用來從序列中獲取值。序列中的元素求值是惰性的,因此使用序列能夠更高效的對集合元素執行鏈式操做。

public interface Sequence<out T> {
    /**
     * Returns an [Iterator] that returns the values from the sequence.
     *
     * Throws an exception if the sequence is constrained to be iterated once and `iterator` is invoked the second time.
     */
    public operator fun iterator(): Iterator<T>
}

執行序列操做:中間和末端操做

序列操做分爲兩類:中間的和末端的。一次 中間操做 返回的是另外一個序列,一個新序列知道如何變換原始序列中的元素;一次 末端操做 返回的是一個結果,這個結果多是集合、元素、數字或者從初始集合的變換序列中獲取的任意對象。

list.asSequence()
        .map(Person::name).filter { it.startsWith("k") }  // 中間操做,始終是惰性的
        .toList()  // 末端操做

建立序列

除了在集合上調用asSequence() 建立序列,還可使用generateSequence函數。

// 計算100之內全部天然數之和
    val naturalNumbers = generateSequence(0) { it + 1 }
    val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }

// 當獲取結果sum時,全部被推遲的操做都被執行
println(numbersTo100.sum()) // 5050

takeWhile 函數在集合中底層源碼:

public inline fun <T> Iterable<T>.takeWhile(predicate: (T) -> Boolean): List<T> {
    val list = ArrayList<T>()
    for (item in this) {
        if (!predicate(item))
            break
        list.add(item)
    }
    return list
}

4. 使用java函數式接口

kotlin的lambda能夠無縫地和java API互操做。

把lambda看成參數傳遞給java方法

能夠把lambda傳給任何指望函數式接口的方法。

//java
void postponeComputation(int delay, Runnable computation)

//kotlin中,能夠把lambda做爲實參傳給它,編譯器會自動轉換成一個Runnable 實例
// "一個Runnable 實例":指的是一個實現了Runnable 接口的匿名類的實例
// 整個程序只會建立一個Runnable實例
postponeComputation(1000) {
    println("kotlin")
}

// 也能夠把對象表達式做爲函數式接口的實現傳遞
// 這種方式每次調用都會建立一個新的實例
postponeComputation(1000,object : Runnable {
    override fun run() {
    }
})

// 編程成全局的變量,程序中僅此一個實例,每次調用時都是同一個對象
val runnable = Runnable { println("kotlin") }
fun handleComputation() {
    postponeComputation(1000, runnable)
}

lambda從包圍它的做用域中捕捉了變量,每次調用就再也不可能重用同一個實例了

fun handleComputation(id: String) {
    // lambda會捕捉id這個變量
    // 每次handleComputation調用時都會建立一個Runnable新實例
    postponeComputation(1000) {
        println(id)
    }
}

SAM構造方法:顯示地把lambda轉換成函數式接口

SAM構造方法 是編譯器生成的函數,讓你執行從lambda到函數式接口實例的顯式轉換。

帶單抽象方法的接口,叫做SAM接口

若是有一個方法返回的是一個函數式接口的實例,不能直接返回一個lambda,要用SAM構造方法把它包裝起來

//  使用SAM構造方法返回值
// SAM構造方法的名稱和底層函數式接口名稱同樣
// SAM構造方法只接收一個參數(一個被用做函數式接口單抽象方法體的lambda),並返回實現了這個接口的類的一個實例
fun callAllDoneRunnable() : Runnable {
    return Runnable { println("All Done.") }
}

callAllDoneRunnable().run()

SAM構造方法還能夠用在須要把從lambda生成的函數式接口實例存儲在一個變量中

// 使用SAM構造方法來重用listener實例
    val listener = OnClickListener { view ->
       // 使用view.id來判斷點擊的是哪個按鈕
        val text = when(view.id) {
            R.id.button1 -> "First Button"
            R.id.button1 -> "Second Button"
            else -> "Unknown Button"
        }
        toast(text)
    }
    
    button1.setOnClickListener(listener)
    button2.setOnClickListener(listener)

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

在lambda函數體內能夠調用一個不一樣對象的方法,並且無須藉助任何額外限定符,這樣的lambda叫做帶接收者的lambda

with函數

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

// 這段代碼調用了result 實例上好幾個不一樣的方法,且每次調用都重複result這個名稱
fun alphabet(): String {
    val result = StringBuilder()
    for (letter in 'A'..'Z') {
        result.append(letter)
    }
    result.append("\nNow I known the alphabet")
    return result.toString()
}

使用 with 函數 重寫上段代碼,with 函數接收兩個參數。

// fun <T, R> with(receiver: T, block: T.() -> R): R
// 使用with構造
fun alphabet(): String {
    val stringBuilder = StringBuilder()
    // 指定接受者的值
    return with(stringBuilder) {
        for (letter in 'A'..'Z') {
            // 經過顯式的this來調用接收者值的方法
            this.append(letter)
        }
        // 省略this能夠調用方法
        append("\nNow I known the alphabet")
        // 從lambda返回值
        this.toString()
    }
}

使用with和一個表達式函數體來構建字母表

// 使用表達式函數體語法
fun alphabet() = with(StringBuilder()) {
    for (letter in 'A'..'Z') {
        append(letter)
    }
    append("\nNow I known the alphabet")
    toString()
}

apply函數

apply 函數始終會返回做爲實參傳遞給它的對象(換句話說:接收者對象)

// 它的接收者變成了做爲實參的lambda接收者,執行apply結果是StringBuilder
fun alphabet() = StringBuilder().apply {
    for (letter in 'A'..'Z') {
        append(letter)
    }
    append("\nNow I known the alphabet")
}.toString()

在java中,一般是經過另一個單獨的Builder對象來完成的;在kotlin中,能夠在任意對象上使用apply函數,徹底不須要自定義對象來完成。

// 使用apply初始化一個TextView
fun createViewWithCustomAttributes(context: Context) = TextView(context).apply {
    text = "Sample Text"
    textSize = 20.0
    setPading(10, 0, 0, 0)
}

可使用kotlin標準庫函數buildString,它會負責建立StringBuilder並調用toString

fun alphabet() = buildString {
    for (letter in 'A'..'Z') {
        append(letter)
    }
    append("\nNow I known the alphabet")
}

若是個人文章對您有幫助,不妨點個贊鼓勵一下(^_^)

相關文章
相關標籤/搜索