博客地址sguotao.top/Kotlin-2018…html
在Kotlin中,高阶函数是指将一个函数做为另外一个函数的参数或者返回值。若是用f(x)、g(x)用来表示两个函数,那么高阶函数能够表示为f(g(x))。Kotlin为开发者提供了丰富的高阶函数,好比Standard.kt中的let、with、apply等,_Collectioins.kt中的forEach等。为了可以自如的使用这些高阶函数,咱们有必要去了解这些高阶函数的使用方法。app
在介绍常见高阶函数的使用以前,有必要先了解函数类型,这对咱们理解高阶函数颇有帮助。Kotlin 使用相似 (Int) -> String 的一系列函数类型来处理函数的声明,这些类型具备与函数签名相对应的特殊表示法,即它们的参数和返回值:less
Kotlin提供了不少高阶函数,这里根据这些高阶函数所在文件的位置,分别进行介绍,先来看一下经常使用的高阶函数,这些高阶函数在Standard.kt文件中。函数
先来看一下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时,就须要针对这些标识进行处理,不然当代码逻辑运行到这些标识处时,就会出现程序的崩溃。ui
先给出run函数的源码:this
/** * 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中的成员变量和成员方法。spa
好比,对一个EditText控件,进行一些设置时:ssr
//email 是一个EditText控件
email.run {
this.setText("请输入邮箱地址")
setTextColor(context.getColor(R.color.abc_btn_colored_text_material))
}
复制代码
先看一下with函数的源码:code
/** * 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))
}
复制代码
看一下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()
}
}
复制代码
看一下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
}
}
复制代码
看一下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))
}
复制代码
看一下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("邮箱地址不能为空")
复制代码
给出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("邮箱地址不能为空")
复制代码
给出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 |