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 > Iterable.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, 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 Iterable.filter(predicate: (T) -> Boolean): List {
return filterTo(ArrayList(), predicate)
}
public inline fun > Iterable.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 Iterable.map(transform: (T) -> R): List {
return mapTo(ArrayList(collectionSizeOrDefault(10)), transform)
}
public inline fun > Iterable.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 :对集合应用判断式
all 和 any 函数检查集合中的所有元素是否都符合某个条件(或者它的变种,是否存在符合的元素);
count 函数检查有多少元素满足判断式;
find 函数返回第一个符合条件的元素。
all函数底层源码:
/**
* Returns `true` if all elements match the given [predicate].
*/
public inline fun Iterable.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 Iterable.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 Iterable.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 Iterable.find(predicate: (T) -> Boolean): T? {
return firstOrNull(predicate)
}
public inline fun Iterable.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 Iterable.groupBy(keySelector: (T) -> K): Map> {
return groupByTo(LinkedHashMap>(), keySelector)
}
public inline fun >> Iterable.groupByTo(destination: M, keySelector: (T) -> K): M {
for (element in this) {
val key = keySelector(element)
val list = destination.getOrPut(key) { ArrayList() }
list.add(element)
}
return destination
}
public inline fun MutableMap.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 Iterable.flatMap(transform: (T) -> Iterable): List {
return flatMapTo(ArrayList(), transform)
}
public inline fun > Iterable.flatMapTo(destination: C, transform: (T) -> Iterable): 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 {
/**
* 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
}
执行序列操作:中间和末端操作
序列操作分为两类:中间的和末端的。一次 中间操作 返回的是另一个序列,一个新序列知道如何变换原始序列中的元素;一次 末端操作 返回的是一个结果,这个结果可能是集合、元素、数字或者从初始集合的变换序列中获取的任意对象。
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 Iterable.takeWhile(predicate: (T) -> Boolean): List {
val list = ArrayList()
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 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")
}
如果我的文章对您有帮助,不妨点个赞鼓励一下(^_^)