当前位置:主页 > 移动开发 > Android代码 >

Kotlin中的高阶函数深入讲解

时间: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

总结

您可能感兴趣的文章:

相关文章