时间:2022-02-03 10:32:21 | 栏目:Android代码 | 点击:次
前言
在Kotlin中,高阶函数是指将一个函数作为另一个函数的参数或者返回值。如果用f(x)、g(x)用来表示两个函数,那么高阶函数可以表示为f(g(x))。Kotlin为开发者提供了丰富的高阶函数,比如Standard.kt中的let、with、apply等,_Collectioins.kt中的forEach等。为了能够自如的使用这些高阶函数,我们有必要去了解这些高阶函数的使用方法。
函数类型
在介绍常见高阶函数的使用之前,有必要先了解函数类型,这对我们理解高阶函数很有帮助。Kotlin 使用类似 (Int) -> String 的一系列函数类型来处理函数的声明,这些类型具有与函数签名相对应的特殊表示法,即它们的参数和返回值:
常用高阶函数
Kotlin提供了很多高阶函数,这里根据这些高阶函数所在文件的位置,分别进行介绍,先来看一下常用的高阶函数,这些高阶函数在Standard.kt文件中。
1.TODO
先来看一下TODO的源码:
/** * Always throws [NotImplementedError] stating that operation is not implemented. */ @kotlin.internal.InlineOnly public inline fun TODO(): Nothing = throw NotImplementedError() /** * Always throws [NotImplementedError] stating that operation is not implemented. * * @param reason a string explaining why the implementation is missing. */ @kotlin.internal.InlineOnly public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason")
TODO函数有两个重载函数,都会抛出一个NotImplementedError的异常。在Java中,有时会为了保持业务逻辑的连贯性,对未实现的逻辑添加TODO标识,这些标识不进行处理,也不会导致程序的异常,但是在Kotlin中使用TODO时,就需要针对这些标识进行处理,否则当代码逻辑运行到这些标识处时,就会出现程序的崩溃。
2.run
先给出run函数的源码:
/** * Calls the specified function [block] and returns its result. */ @kotlin.internal.InlineOnly public inline fun <R> run(block: () -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() } /** * Calls the specified function [block] with `this` value as its receiver and returns its result. */ @kotlin.internal.InlineOnly public inline fun <T, R> T.run(block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() }
这两个run函数都接收一个lambda表达式,执行传入的lambda表达式,并且返回lambda表达式的执行结果。区别是T.run()是作为泛型T的一个扩展函数,所以在传入的lambda表达式中可以使用this关键字来访问这个泛型T中的成员变量和成员方法。
比如,对一个EditText控件,进行一些设置时:
//email 是一个EditText控件 email.run { this.setText("请输入邮箱地址") setTextColor(context.getColor(R.color.abc_btn_colored_text_material)) }
3.with
先看一下with函数的源码:
/** * Calls the specified function [block] with the given [receiver] as its receiver and returns its result. */ @kotlin.internal.InlineOnly public inline fun <T, R> with(receiver: T, block: T.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return receiver.block() }
with函数有两个参数,一个类型为泛型T类型的receiver,和一个lambda表达式,这个表达式会作为receiver的扩展函数来执行,并且返回lambda表达式的执行结果。
with函数与T.run函数只是写法上的不同,比如上面的示例可以用with函数:
with(email, { setText("请输入邮箱地址") setTextColor(context.getColor(R.color.abc_btn_colored_text_material)) }) //可以进一步简化为 with(email) { setText("请输入邮箱地址") setTextColor(context.getColor(R.color.abc_btn_colored_text_material)) }
4.apply
看一下apply函数的源码:
/** * Calls the specified function [block] with `this` value as its receiver and returns `this` value. */ @kotlin.internal.InlineOnly public inline fun <T> T.apply(block: T.() -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block() return this }
apply函数作为泛型T的扩展函数,接收一个lambda表达式,表达式的receiver是泛型T,没有返回值,apply函数返回泛型T对象本身。可以看到T.run()函数也是接收lambda表达式,但是返回值是lambda表达式的执行结果,这是与apply函数最大的区别。
还是上面的示例,可以用apply函数:
email.apply { setText("请输入邮箱地址") }.apply { setTextColor(context.getColor(R.color.abc_btn_colored_text_material)) }.apply { setOnClickListener { TODO() } }
5.also
看一下also函数的源码:
/** * Calls the specified function [block] with `this` value as its argument and returns `this` value. */ @kotlin.internal.InlineOnly @SinceKotlin("1.1") public inline fun <T> T.also(block: (T) -> Unit): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } block(this) return this }
与apply函数类似,也是作为泛型T的扩展函数,接收一个lambda表达式,lambda表达式没有返回值。also函数也返回泛型T对象本身,不同的是also函数接收的lambda表达式需要接收一个参数T,所以在lambda表达式内部,可以使用it,而apply中只能使用this。
关于this和it的区别,总结一下:
还是上面的示例,如果用also函数:
email.also { it.setText("请输入邮箱地址") }.also { //可以使用其它名称 editView -> editView.setTextColor(applicationContext.getColor(R.color.abc_btn_colored_text_material)) }.also { it.setOnClickListener { //TODO } }
6.let
看一下let函数的源码:
/** * Calls the specified function [block] with `this` value as its argument and returns its result. */ @kotlin.internal.InlineOnly public inline fun <T, R> T.let(block: (T) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block(this) }
let函数作为泛型T的扩展函数,接收一个lambda表达式,lambda表达式需要接收一个参数T,存在返回值。lambda表达式的返回值就是let函数的返回值。由于lambda表达式接受参数T,所以也可以在其内部使用it。
let应用最多的场景是用来判空,如果上面示例中的EditText是自定义的可空View,那么使用let就非常方便:
var email: EditText? = null TODO() email?.let { email.setText("请输入邮箱地址") email.setTextColor(getColor(R.color.abc_btn_colored_text_material)) }
7.takeIf
看一下takeIf函数的源码:
/** * Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't. */ @kotlin.internal.InlineOnly @SinceKotlin("1.1") public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? { contract { callsInPlace(predicate, InvocationKind.EXACTLY_ONCE) } return if (predicate(this)) this else null }
takeIf函数作为泛型T的扩展函数,接受一个lambda表达式,lambda表达式接收一个参数T,返回Boolean类型,takeIf函数根据接收的lambda表达式的返回值,决定函数的返回值,如果lambda表达式返回true,函数返回T对象本身,如果lambda表达式返回false,函数返回null。
还是上面的示例,假设用户没有输入邮箱地址,进行信息提示:
email.takeIf { email.text.isEmpty() }?.setText("邮箱地址不能为空")
8.takeUnless
给出takeUnless函数的源码:
/** * Returns `this` value if it _does not_ satisfy the given [predicate] or `null`, if it does. */ @kotlin.internal.InlineOnly @SinceKotlin("1.1") public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? { contract { callsInPlace(predicate, InvocationKind.EXACTLY_ONCE) } return if (!predicate(this)) this else null }
takeUnless函数与takeIf函数类似,唯一的区别是逻辑相反,takeUnless函数根据lambda表达式的返回值决定函数的返回值,如果lambda表达式返回true,函数返回null,如果lambda表达式返回false,函数返回T对象本身。
还是上面的示例,如果用takeUnless实现,就需要调整一下逻辑:
email.takeUnless { email.text.isNotEmpty() //与takeIf的区别 }?.setText("邮箱地址不能为空")
9.repeat
给出repeat函数的源码:
/** * Executes the given function [action] specified number of [times]. * * A zero-based index of current iteration is passed as a parameter to [action]. */ @kotlin.internal.InlineOnly public inline fun repeat(times: Int, action: (Int) -> Unit) { contract { callsInPlace(action) } for (index in 0 until times) { action(index) } }
repeat函数接收两个参数,一个Int型参数times表示重复次数,一个lambda表达式,lambda表达式接收一个Int型参数,无返回值。repeat函数就是将我们传入的lambda表达式执行times次。
repeat(3) { println("执行第${it + 1}次") } //运行结果 执行第1次 执行第2次 执行第3次
由于repeat函数接收的lambda表达式,需要一个Int型参数,因此在表达式内部使用it,其实it就是for循环的索引,从0开始。
总结
最后对这些高阶函数做一下总结,TODO对比Java中的TODO,需要实现业务逻辑,不能放任不理,否则会出现异常,导致崩溃。takeIf、takeUnless这一对都是根据接收lambda表达式的返回值,决定函数的最终返回值是对象本身,还是null,区别是takeIf,如果lambda表达式返回true,返回对象本身,否则返回null;takeUnless与takeIf的逻辑正好相反,如果lambda表达式返回true,返回null,否则返回对象本身。repeat函数,见名知意,将接收的lambda表达式重复执行指定次。
run、with、apply、also、let这几个函数区别不是很明显,有时候使用其中一个函数实现的逻辑,完全也可以用另外一个函数实现,具体使用哪一个,根据个人习惯。需要注意的是:
对这几个函数的区别做一个对比:
函数名称 | 是否作为扩展函数 | 是否返回对象本身 | 在函数内部使用this/ it |
---|---|---|---|
run | no | no | - |
T.run | yes | no | it |
with | no | no | this |
apply | yes | yes | this |
also | yes | yes | it |
let | yes | no | it |
总结