Kotlin中的高阶函数
博客地址sguotao.top/Kotlin-2018…
在Kotlin中,高阶函数是指将一个函数作为另一个函数的参数或者返回值。如果用f(x)、g(x)用来表示两个函数,那么高阶函数可以表示为f(g(x))。Kotlin为开发者提供了丰富的高阶函数,比如Standard.kt中的let、with、apply等,_Collectioins.kt中的forEach等。为了能够自如的使用这些高阶函数,我们有必要去了解这些高阶函数的使用方法。
函数类型
在介绍常见高阶函数的使用之前,有必要先了解函数类型,这对我们理解高阶函数很有帮助。Kotlin 使用类似 (Int) -> String 的一系列函数类型来处理函数的声明,这些类型具有与函数签名相对应的特殊表示法,即它们的参数和返回值:
- 所有函数类型都有一个圆括号括起来的参数类型列表以及一个返回类型:(A, B) -> C 表示接受类型分别为 A 与 B 两个参数并返回一个 C类型值的函数类型。参数类型列表可以为空,如 () -> A ,返回值为空,如(A, B) -> Unit;
- 函数类型可以有一个额外的接收者类型,它在表示法中的点之前指定,如类型 A.(B) -> C 表示可以在 A 的接收者对象上,调用一个以 B 类型作为参数,并返回一个 C 类型值的函数。
- 还有一种比较特殊的函数类型,挂起函数,它的表示法中有一个 suspend 修饰符 ,例如 suspend () -> Unit 或者 suspend A.(B) -> C 。
常用高阶函数
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的区别,总结一下:
- 如果泛型T,作为lambda表达式的参数,形如:(T) -> Unit,此时在lambda表示内部使用it;
- 如果泛型T,作为lambda表达式的接收者,形如:T.() -> Unit,此时在lambda表达式内部使用this;
- 不论this,还是it,都代表T对象,区别是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? = nullTODO()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这几个函数区别不是很明显,有时候使用其中一个函数实现的逻辑,完全也可以用另外一个函数实现,具体使用哪一个,根据个人习惯。需要注意的是:
- 对作为扩展函数的高阶函数,使用前需要判断接收的对象是否为空,比如T.run,apply,also,let在使用前需要进行空检查;
- 对于返回对象本身的函数,比如apply,also可以形成链式调用;
- 对于在函数内部能够使用it的函数,it可以用意思更加清晰的变量代替,比如T.run,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 |
学习资料
- Kotlin Bootcamp for Programmers
- Kotlin Koans
转载于:https://juejin.im/post/5be16ae8f265da61141c12b1
Kotlin中的高阶函数相关推荐
- 【Kotlin】Kotlin 语言集合中的高阶函数详解 ( 数据类 data class | maxBy | minBy | filter | map | any | count | find )
文章目录 I . List 集合高阶函数引入 II . Kotlin 数据类 ( data class ) III . Java 代码 与 Kotlin 代码实现对比 ( 查询年龄最大的 ) IV . ...
- python中的高阶函数
python中的高阶函数 文章目录: 1 什么是高阶函数? 1.1 高阶函数:一个函数的`函数名`作为参数传给另外一个函数 1.2 高阶函数:一个函数返回值(return)为另外一个`函数` 2 py ...
- 16、react 中的高阶函数和柯里化
react 中的高阶函数和柯里化 这一篇博文我们说一下 高阶函数 和 柯里化,这两个次可能第一次听说,不知道是啥意思,我们先不管他哈,记得上一篇博客,我们实现了一个登陆的案例是吧?输入用户名和密码,点 ...
- scala中的高阶函数_Scala中的高阶函数(HOF)
scala中的高阶函数 Higher Order Functions (HOF) in Scala are the very core of this functional programming l ...
- Swift 中的高阶函数
一.概念 高阶函数是将一个或多个函数作为参数或返回一个函数作为其结果的函数 二.Swift的集合类型中的高阶函数 1.Map 对于原始集合里的每一个元素,以一个变换后的元素替换之形成一个新的集合 le ...
- kotlin修炼指南8—集合中的高阶函数
点击上方蓝字关注我,知识会给你力量 Kotlin对集合操作类新增了很多快捷的高阶函数操作,各种操作符让很多开发者傻傻分不清,特别是看一些Kotlin的源码或者是协程的源码,各种眼花缭乱的操作符,让代码 ...
- python什么是高阶函数_说说 Python 中的高阶函数
高阶函数(higher-order function)指的是:接受一个函数为参数,或者把函数作为结果值返回的函数1. 1 sorted() 比较常见的高阶函数是 sorted(),其内部的关键字参数 ...
- 1 access中iif函数中的_JavaScript中的高阶函数
前言 在 JavaScript 的学习过程中,我们可能或多或少地接触过高阶函数.那么,我们自己对此是否有一个明确的定义,或者说很熟练的掌握这些用法呢 如果文章中有出现纰漏.错误之处,还请看到的小伙伴多 ...
- ES6中的高阶函数:如同 a = b = c 一样简单
2019独角兽企业重金招聘Python工程师标准>>> 作者:Sequoia McDowell 2016年01月16日 ES6来啦!随着越来越多的代码库和思潮引领者开始在他们的代码中 ...
最新文章
- java算法 第七届 蓝桥杯B组(题+答案) 5.抽签
- 应运而生的web页面响应布局
- CTF中遇到不知道文件类型_AE中你不知道的隐藏功能-Mask与特效
- python三个数求和_Python:list是否包含3个连续的整数,总和为7...
- java验证码工具_java 验证码工具
- 《深入理解OSGi:Equinox原理、应用与最佳实践》一3.4 事件监听
- 演示和解析Flex布局中的各种属性
- 最好用的 网络神偷V10.7
- ! [remote rejected] master -> master (pre-receive hook declined)
- docker安装时报服务失败,因为控制进程退出并带有错误代码
- Python学习笔记(十五):python 中的面向对象
- 测试化妆品真假软件,查询化妆品真伪的app叫什么
- python教室实验室预约系统毕业设计开题报告
- 使用Selenium时,如何选择ChromeDriver驱动版本对应Chrome浏览器版本
- 盘点气压传感器在智能手机中应用场景 智芯传感板装式压力传感器提升产品性能
- 2022不一样的分享--行走的皮卡丘
- LUA脚本扩展wireshark自定义的协议
- Kubernetes Pod Evicted
- memcache 学习
- 图像配准(匹配)与变化检测
热门文章
- Python架构(二)
- Android复合控件创建与使用Demo
- PHP 从数组对象中取出数组提示:Undefined property: stdClass::$subject
- 使用Freemarker来页面静态化,与Spring整合使用
- 如何实现一套可切换的声网+阿里的直播引擎
- Drop Table对MySQL的性能影响分析
- You are running Composer with SSL/TLS protection disabled.
- 进程分析命令(持续更新中)
- 【机器学习实战】Machine Learning in Action 代码 视频 项目案例
- 在Linux 环境下搭建 JDK 和 Tomcat