博客主页

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表达式存储在一个变量中,把这个变量当作普通函数对待

 // 使用变量存储lambda,不能推导出参数类型,必须显式的指定参数类型
 val sum = { x: Int, y: Int -> x + y }
 println(sum(1, 2)) // 调用保存在变量中的lambda

kotlin中如果lambda表达式是函数调用的最后一个实参,它可以放到括号的外边

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

// 当lambda是函数唯一的实参时,可以去掉调用代码中的空括号
// 显式的写出参数类型
list.maxBy { person: Person ->
        person.age
    }

// 可以不写参数类型,会根据上下文推导出参数类型
list.maxBy { person -> person.age }

使用命名实参来传递lambda

    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捕捉。

成员引用

// 成员引用语法,使用::运算符
// 类::成员
Person::age

:: 运算符 可以把函数转换成一个值,如:val getAge = Person::age ,这种表达式称为成员引用

还可以引用顶层函数(不是类的成员)

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

如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)


小兵兵同学
56 声望23 粉丝

Android技术分享平台,每个工作日都有优质技术文章分享。从技术角度,分享生活工作的点滴。