在 Kotlin 源码的 Standard.kt 文件中提供了一些很好用的内置高阶函数,可以帮助我们写出更优雅的 Kotlin 代码,提高生产力。为了能学习这些高阶函数,有必要先对高阶函数、Lambda表达式有所了解。
接下来我们逐个学习,其中 let、also、with、run、apply 这几个函数的功能很相似,需要我们重点注意,按需使用。
一、 let
let 函数的声明如下:
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
可以看出 let 是一个作用域函数,需要通过一个对象来调用,参数是函数类型,同时 let 函数的返回值类型也是该函数的返回值类型。由于我们一般会用 Lambda 表达式作为函数类型参数的值,那么 let 函数的返回值就是 Lambda 表达式的返回值,以下内容都会采用类似的说法,这一点需要注意。
一个典型的使用场景就是创建一个目标 Activity、Fragment 并接收参数时,可以考虑使用 let 函数,例如在 Fragment 中接收参数时:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
在arguments
不为空时,Lambda 表达式内it
就代替arguments
对象来访问其方法。
二、also
also 函数的声明如下:
@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
}
also 函数从 Kotlin1.1开始支持,和 let 函数的声明比较一下,其实是很类似的,唯一的区别就是返回值不同,前边我已经知道 let 函数的返回值可以是 Lambda 表达式 的返回值,而 also 函数的返回值是调用 also 函数的对象:
fun main(args: Array<String>) {
val let = "kotlin".let {
it.toUpperCase()
}
val also = "kotlin".also {
it.toUpperCase()
}
println("let的返回值:$let")
println("also的返回值:$also")
}
// 输出
let的返回值:KOTLIN
also的返回值:kotlin
所以除了返回值的差别外,also 函数适合 let 函数的任何使用场景,另外 also 函数更适合链式操作一个对象的属性、方法,并返回该对象的场景:
data class User(var name: String = "", var age: Int = 0, var sex: String = "") {
override fun toString(): String {
return "name:$name,age:$age,sex:$sex"
}
}
fun main(args: Array<String>) {
val user2 = User().also {
it.name = "Tom"
it.age = 18
it.sex = "male"
}
println(user2.toString())
}
// 输出
name:Tom,age:18,sex:male
三、with
with 函数的声明如下:
@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 函数需要两个参数,需要操作的对象和一个函数类型的参数(一般是 Lambda 表达式),在 Lambda 表达式中可以用this
指代要操作的对象或者省略 this 也行,返回值就是 Lambda 表达式的返回值,虽然这个返回值一般没啥用。
当我们需要调用一个对象的多个方法时,为了简化写法可以省略掉多个对象名称,这是可以考虑使用 with 函数,例如 Recycleriew 中绑定 ViewHolder 的操作,一般情况是这样的:
override fun convert(viewHolder: ViewHolder, data: DatasItem, position: Int) {
ImageLoader.load(mContext, data.envelopePic, viewHolder.getView(R.id.projectIv))
viewHolder.setText(R.id.projectTitleTv, Html.fromHtml(data.title).toString())
viewHolder.setText(R.id.projectDescTv, data.desc)
viewHolder.setText(R.id.projectAuthorTv, data.author)
viewHolder.setText(R.id.projectTimeTv, data.niceDate)
}
如果使用了 with 函数会是这样的:
override fun convert(viewHolder: ViewHolder, data: DatasItem, position: Int) {
with(viewHolder){
ImageLoader.load(mContext, data.envelopePic, getView(R.id.projectIv))
setText(R.id.projectTitleTv, Html.fromHtml(data.title).toString())
setText(R.id.projectDescTv, data.desc)
setText(R.id.projectAuthorTv, data.author)
setText(R.id.projectTimeTv, data.niceDate)
}
}
四、run
run 函数的声明如下:
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
两种声明,第一个没啥大用,直接看第二个,是不是有点像 let、 with 函数的结合体呢!需要通过一个对象调用,可已接收一个 Lambda 表达式作为参数,那么返回值自然是 Lambda 表达式的返回值。
和 with 函数类似在 Lambda 表达式中可以用this
指代要操作的对象或者省略 this 也行,而无需使用it
,同时也具备了 let 函数可以进行对象判空的优点,例如 Android 中 Toolbar 的初始化操作:
toolbar.run {
title = "设置"
setSupportActionBar(this)
setNavigationOnClickListener {
finish()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
将相关的操作集中在一个代码块里,代码逻辑会更加的清晰。
五、apply
apply 函数的声明如下:
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
apply 函数和 run 函数很像,唯一的区别就是 apply 函数返回调用它的对象本身。一般情况下,如果需要创建一个对象,并在相关初始化操作后赋值给一个变量可以考虑使用 apply 函数。例如 Fragment 的 newInstance()方法传递参数时:
companion object {
@JvmStatic
fun newInstance(param1: String, param2: String) =
TestFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
其实,这五个函数中,根据是否需要对象的返回值来划分需求,只使用 run、apply 函数就可以替代其它函数的使用场景。当然合适的才是最好的,按需选择即可!它们的主要语法差别的如下:
函数 | Lambda 表达式中如何指代当前对象 | 返回值 |
---|---|---|
let | it | Lambda 表达式的值 |
also | it | 当前对象 |
with | this(可省略) | Lambda 表达式的值 |
run | this(可省略) | Lambda 表达式的值 |
aplly | this(可省略) | 当前对象 |
六、takeIf
takeIf 函数的声明如下:
@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
}
我们可以通过一个对象来调用它,如果predicate
函数返回值为true
,则返回调用对象,否则返回null
,注意predicate
函数的参数就是当前调用对象。
这其实就是一个加强版的if
表达式,更加灵活,我们可以让对象使用安全调用操作符?.
,由于 takeIf 函数可以返回对象本身,那么自然可以进行链式调用。写个例子简单比较下:
fun filterUser1(user: User?) {
if (user != null && user.age > 18 && user.sex == "male") {
println(user.toString())
}
}
fun filterUser2(user: User?) {
user?.takeIf {
it.age > 18 && it.sex == "male"
}.apply {
println(toString())
}
}
七、takeUnless
takeUnless 函数的声明如下:
@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
}
嗯?如果predicate
函数返回值为false
,则返回调用对象,否则返回null
,功能和 takeIf 函数相反!
八、repeat
repeat 函数声明如下:
@kotlin.internal.InlineOnly
public inline fun repeat(times: Int, action: (Int) -> Unit) {
contract { callsInPlace(action) }
for (index in 0 until times) {
action(index)
}
}
就是将action
函数执行times
次,函数的参数就是当前的次数:
fun main(args: Array<String>) {
repeat(6) {
println("Kotlin$it")
}
}
// 输出
Kotlin0
Kotlin1
Kotlin2
Kotlin3
Kotlin4
Kotlin5
九、TODO
TODO 函数的声明如下:
@kotlin.internal.InlineOnly
public inline fun TODO(): Nothing = throw NotImplementedError()
@kotlin.internal.InlineOnly
public inline fun TODO(reason: String): Nothing = throw NotImplementedError("An operation is not implemented: $reason")
和 Java 中的TODO
类似,可以用来标注某个方法需要重写,或者没有完成的事项等等,但是 Kotlin 的 TODO 会抛出异常,并可以指定异常原因!
网友评论