【Kotlin】Kotlin函数那么多,你会几个?
目录
- 标准函数
- let
- run
- with
- apply
- also
- takeIf
- takeUnless
- repeat
- 小结
- 作用域函数的区别
- 作用域函数使用场景
- 简化函数
- 尾递归函数(tailrec)
- 扩展函数
- 高阶函数
- 内联函数(inline)
- inline
- noinline
- crossinline
- 匿名函数
标准函数
Kotlin标准库包含几个函数,其唯一目的是在对象上下文中执行代码块。当对提供了lambda表达式的对象调用这样的函数时,它会形成一个临时作用域。在此范围内,您可以访问不带名称的对象。此类函数称为作用域函数。从技术上讲,作用域函数在许多情况下是可以互换使用的。
let
上下文对象可用作参数(
it),引用对象时使用it.。返回值是lambda表达式最后一行代码的结果。可用于调用调用链结果上的一个或多个函数。
var user = User()
// 使用此值作为参数调用指定的函数块并返回其结果。
val let = user.let {it.name = "宾有为"it.func = "let"1 // 返回值 1
}
print("let:${user},return lambda result:${let}")
执行结果:

run
上下文对象可用作接收器(
this),引用对象使用this.或直接使用对象值。返回值是lambda表达式最后一行代码的结果。run执行与with相同的操作,但作为上下文对象的扩展函数调用let。当lambda同时包含对象初始化和返回数值时,run非常有用。
var user = User()
// 调用指定的函数块,将此值作为其接收器并返回其结果。
val run = user.run {name = "宾有为"func = "run"1 // 返回值 1
}
print("run:${user}\nreturn lambda result:${run}")
执行结果:

with
非扩展函数:上下文对象作为参数传递,但在
lambda内部,它作为接收器(this)可用。返回值是lambda表达式最后一行代码的结果。建议使用来调用上下文对象上的函数,而不提供lambda结果。
var user = User()
// 以给定的接收器作为其接收器调用指定的函数块,并返回其结果。
val with = with(user) {name = "宾有为"func = "with"1 // 返回值 1
}
print("run:${user}\nreturn lambda result:${with}")
执行结果:

apply
上下文对象可用作接收器(
this),引用对象使用this.或直接使用对象值。返回值是对象本身。对于不返回值且主要对接收器对象的成员进行操作的代码块,请使用apply。apply的常见情况是对象配置。
var user = User()
// 使用此值作为其接收器调用指定的函数块,并返回此值。
val apply = user.apply {name = "宾有为"func = "apply"
}
print("also:${apply}\nreturn context object:${apply}")
执行结果:

also
上下文对象可用作参数(
it),引用对象时使用it.。返回值是对象本身。也适用于执行一些将上下文对象作为参数的操作。还可用于需要引用对象而不是其属性和函数的操作,或者不希望从外部范围隐藏此引用时。
var user = User()
// 使用此值作为参数调用指定的函数块并返回此值。
val also = user.also {it.name = "宾有为"it.func = "also"
}
print("also:${user}\nreturn context object:${also}")
执行结果:

takeIf
takeIf是类似if关键字单个对象的过滤函数,使用方式与takeUnless相反。使用对象进行调用时,如果该对象与lambda的条件匹配,takeIf将返回该对象。否则,返回null。
var user = User(name = "宾有为", func = null)
val existName = user.takeIf { it.name != null }
val existSex = user.takeIf { it.func != null }
println("existName: $existName, existSex: $existSex")
执行结果:

takeUnless
takeIf是类似else关键字的过滤函数,使用方式与takeIf相反。使用对象进行调用时,如果该对象与lambda的条件匹配,takeIf将返回该对象。否则,返回null。
var user = User(name = "宾有为", func = null)
val existName = user.takeUnless { it.name != null }
val existSex = user.takeUnless { it.func != null }
println("existName: $existName, existSex: $existSex")
执行结果:

repeat
repeat,是一个从0开始循环至指定长度的函数,与for (index in 0 until times) { }执行的结果一致。
// 从0遍历至10
repeat(10){print(it)
}
执行结果:

小结
Kotlin标准库包含几个函数,其唯一目的是在对象上下文中执行代码块。当对提供了lambda表达式的对象调用这样的函数时,它会形成一个临时作用域。在此范围内,您可以访问不带名称的对象。此类函数称为作用域函数。作用域函数有五个:let、run、with、apply和also。
作用域函数的区别

作用域函数使用场景
| 函数 | 使用场景 |
|---|---|
| let | 1、对非空对象执行lambda 2、在局部范围中引入表达式作为变量 |
| with | 对对象的函数调用进行分组 |
| run | 1、对象配置和计算结果 2、在需要表达式的地方运行语句 |
| apply | 1、对象配置和计算结果 2、不返回值且主要对接收器对象的成员进行操作的代码块 |
| also | 1、执行一些将上下文对象作为参数的操作。 2、需要引用对象而不是其属性和函数的操作 3、不希望从外部范围隐藏此引用时 |
简化函数
在
kotlin中,变量可以通过等号赋值,函数同样被允许使用等号进行赋值,这些使用等号赋值的函数就叫做简化函数。
简化函数的编写规范是有所要求的,如果函数表达式只有一行,才可以使用等号赋予函数其表达式。简化函数默认return函数的最后一行代码。
fun main(args: Array<String>) {println(test1())// result:2println(test2())// result:简化函数
}
// 执行表达式 1+1
private fun test1() = 1+1
// 返回表达式"简化函数",简化函数如果有返回类型,表达式的执行结果必须是一个可以返回的类型。
private fun test2() : String = "简化函数"
尾递归函数(tailrec)
kotlin存在一种特殊的递归函数——尾递归函数,指的是函数末尾的返回值重复调用了自身函数。使用时只需要在函数的fun前面加上tailrec关键字,编译器在编译时会自动优化递归,使用循环方式代替递归,从而避免栈溢出的情况,以此提高程序性能。
fun main(args: Array<String>) {print(factorial(5)) // result:120
}// 求 i 的阶乘( i * i-1 )
tailrec fun factorial(i: Int): Int {if (i != 1) {return i * factorial(i - 1)} else {return i}
}
扩展函数
把接收者类型,放到即将添加的函数前面,通过类可以对其调用的函数称为扩展函数。
如图所示,接收者类型写在了test函数的前边,在扩展函数里,使用this引用的是接收者l类型的对象,而非当前Test类。

如下图所示,通过String类型还无法调用test函数,我们在函数名称的前面加上String类型,再次通过类型就可以引用test函数。


扩展函数只能由接收者类型调用,不能通过扩展函数所在的类调用。

扩展函数不仅可以扩展函数,还可以扩展属性。
val String.lastIndex: Intget() = 0fun main(args: Array<String>) {var a = "aaaa000"print(a.lastIndex) // result:0
}
小知识
- 扩展函数不可以重写。
- 扩展函数实质上是静态函数。
- 在扩展函数里使用
this引用的是扩展函数的类型,而不是函数当前所在类。 - 如果扩展函数在其接收者类型之外声明,则它不能访问接收者
private或protected成员。 - 扩展函数、属性的代码优先级高于接收者类型原有的函数、对象,等同于重写接收者的同名函数、属性。
高阶函数
高阶函数是将函数作为参数或返回函数的函数。
在以下的示例代码中,testFun1(i: Int, face: (String) -> String)就是一个高阶函数,因为它接受一个函数值作为它的第二个参数。
fun main(args: Array<String>) {testFun1(1) { it ->// 实现业务逻辑,将it给return回去it}
}// face: (String) -> String 等于 调用高阶函数使用的名称: (需要传递的参数类型) -> 返回类型
fun testFun1(i: Int, face: (String) -> String) {// 接收到face返回值,并将其printval value = face("testFun")println("testFun:$value")
}
在实现高阶函数的lambda表达式里(如图main函数对testFun1的调用),若设置有返回值,则默认return最后一行的代码结果,若不设置,则返回Unit。
运行结果:

高阶函数除了上述使用方式之外,还可以根据需求传入不同函数对逻辑进行处理。
fun main(args: Array<String>) {val plusResult = num1AndNum2(20, 30) { n1: Int, n2: Int ->// 执行运算 n1 + n2}println("$plusResult") // result:50val minusResult = num1AndNum2(20, 30) { n1: Int, n2: Int ->// 执行运算n1 - n2}println("$minusResult") // result:-10
}fun num1AndNum2(num1: Int, num2: Int, block: (Int, Int) -> Int): Int {return block(num1, num2)
}
除此之外,高阶函数的lambda表达式还可以拆分成lambda表达式变量、匿名函数,分别以变量、匿名函数的形式传入高阶函数。
fun main(args: Array<String>) {val minusResult = num1AndNum2(20, 30, lambda1)println("$minusResult") // result:-10val aaa = num1AndNum2(20, 30, lambda2) // result:50println("$aaa")val anonymous1 = num1AndNum2(20, 30, a) // result:-10val anonymous2 = num1AndNum2(20, 30, b) // result:50// 匿名函数num1AndNum2(20,30,fun(x,y) = x + y) // result:50
}
// lambda表达式
val lambda1 = { x: Int, y: Int -> x - y }
// lambda表达式
val lambda2: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
// 匿名函数
val a = fun(x : Int, y : Int): Int = x - y
// 匿名函数
val b = (fun(x : Int, y : Int): Int = x + y)fun num1AndNum2(num1: Int, num2: Int, block: (Int, Int) -> Int): Int {return block(num1, num2)
}
内联函数(inline)
inline
lambda表达式在底层被转换成了匿名内部类的实现方式,每调用一次lambda表达式,都会创建一个新的匿名类实例,会造成额外的内存和性能开销,但这种开销可以通过inlinelambda表达式来消除。使用inline关键字修饰的函数也被称为内联函数。
使用inline关键字,需结合反编译才能看见效果。
fun inlineFun(action: (() -> Unit)){println("inlineFun: 调用前...")action()println("inlineFun: 调用后...")
}fun main(args: Array<String>) {inlineFun {println("inlineFun: 正在调用...")}
}
使用Idea、Android Studio开发工具反编译代码步骤:在开发工具的顶部菜单栏 Tools > Kotlin > Show Kotlin Bytecodes > Decompile。
inlineFun未添加inline函数代码反编译结果:

inlineFun添加inline函数代码反编译结果:

通过两次添加inline关键字前后的反编译比对,可以看出inline函数是将表达式转移到调用方,通过这样的方式减少lambda创建新匿名类造成的开销。
当你尝试在没有lambda表达式的函数上使用inline时,编译器则会提示inline对性能预期影响微不足道,应该结合高阶函数一起使用的警告。
Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types.
需要注意的是,inline函数不支持修饰变量(包含持有高阶函数的变量),只能用于修饰高阶函数。
noinline
如果不希望传递给
inline函数的所有lambda都被内联,请使用修饰符noinline标记一些不需要inline的函数参数(仅限lambda表达式)。使用noinline的前提是使用的函数必须是inline函数。
inline fun inlineFun(noinline action: (() -> Unit)){println("inlineFun: 调用前...")action()println("inlineFun: 调用后...")
}fun main(args: Array<String>) {inlineFun{println("inlineFun: 调用中...")}
}
添加noinline代码反编译后,“调用中…”的字符并没有被反编译出来,而是用action$iv.invoke();调用函数。

crossinline
crosinline用于禁止传递给内联函数的lambda中的非局部返回。
在内联函数的lambda表达式里,使用return会中断高阶函数后面代码的执行,讲inline函数有讲解到:inline函数是将表达式转移到调用方,因此下面代码的“调用后”是不会print。
inline fun inlineFun(action: (() -> Unit)) {println("inlineFun: 调用前...")action()println("inlineFun: 调用后...")
}fun main(args: Array<String>) {inlineFun {println("inlineFun: 调用中...")return}
}

使用crossinline关键字,则可以杜绝这种情况。inline函数添加crossinline后,return将会报'return' is not allowed here的错误。

部分博客把crossinline的含义解释成“检查代码中是否有return,如果有则会执行不通过”。这样的说法是片面的,官方文档已经解释了,是禁止非局部返回。如果高阶函数里面的return不影响到嗲用高阶函数的函数后面代码执行,是可以使用return,如下:


匿名函数
省略了函数名字的函数称之为匿名函数。
匿名函数往往结合高阶函数一起使用,匿名函数默认隐式return最后一行代码,写法有以下三种:
val a = fun(str: String): String = "a"
val b = (fun(str: String): String = "b")
参数和返回类型的指定方式与高阶函数相同,如果可以从上下文中推断出参数类型,则可以省略参数类型。如下:
// 匿名函数
println(fun(item) = "c")
// 高阶函数
fun println(str : (String) -> String){println(str("ttt"))
}
匿名函数与lambda表达式类似,写法不同,但执行效果可以是一致的,如下lambda函数等同于上面的匿名函数写法:
var d: (it: String) -> String = { "d" }var e: (String) -> String = { "e" }var f = { it: String -> "f" }
看了好几篇的博客,大部分都是说lambda表达式就是匿名函数,我在kotlin官方文档找到的匿名函数写法并不包含lambda表达式,同时也不知道其它作者所表达的lambda表达式就是匿名函数的依据来自于哪里。
在官方文档的匿名函数介绍里,官方讲解了匿名函数与lambda表达式的区别:lambda表达式和匿名函数之间的另一个区别是非本地返回的行为。没有标签的return语句总是从用fun关键字声明的函数返回。这意味着lambda表达式内的返回将从封闭函数返回,而匿名函数内的返回则从匿名函数本身返回。

小结
- 将匿名函数作为参数传递时,将它们放在括号内。允许您将函数放在括号外的速记语法仅适用于
lambda表达式。
参考文档
1、高阶函数
2、标准、作用域函数
3、Kotlin之高阶函数
4、《Kotlin从零到精通》—— 函数的运用
5、Android筑基,Kotlin扩展函数详解——郭霖
6、Kotlin Doc——Extensions
7、Kotlin Keywords and operators
相关文章:
【Kotlin】Kotlin函数那么多,你会几个?
目录标准函数letrunwithapplyalsotakeIftakeUnlessrepeat小结作用域函数的区别作用域函数使用场景简化函数尾递归函数(tailrec)扩展函数高阶函数内联函数(inline)inlinenoinlinecrossinline匿名函数标准函数 Kotlin标准库包含几个…...
饲养员喂养动物-课后程序(JAVA基础案例教程-黑马程序员编著-第四章-课后作业)
【案例4-2】饲养员喂养动物 记得 关注,收藏,评论哦,作者将持续更新。。。。 【案例目标】 案例描述 饲养员在给动物喂食时,给不同的动物喂不同的食物,而且在每次喂食时,动物都会发出欢快的叫声。例如&…...
数据分析:消费者数据分析
数据分析:消费者数据分析 作者:AOAIYI 创作不易,如果觉得文章不错或能帮助到你学习,记得点赞收藏评论一下哦 文章目录数据分析:消费者数据分析一、前言二、数据准备三、数据预处理四、个体消费者分析五、用户消费行为总…...
Transformer论文阅读:ViT算法笔记
标题:An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale 会议:ICLR2021 论文地址:https://openreview.net/forum?idYicbFdNTTy 文章目录Abstract1 Introduction2 Related Work3 Method3.1 Vision Transformer3.2…...
Android基础练习解答【2】
文章目录一 填空题二 判断题三 选择题四 简答题一 填空题 1.除了开启开发者选项之外,还需打开手机上的 usb调试 开关,然后才能在手机上调试App。 2.App开发的两大技术路线包括 _原生开发_和混合开发。 3.App工程的编译…...
k8s 搭建
需求:搭建k8s 为后续自动部署做准备进程:安装至少两个ubuntu18.04系统(一个master 一到多个 node)每个系统上都要装上docker 和 kubernetes安装dockersudo su apt-get update#安装相关插件 apt-get install apt-transport-https c…...
安全运维之mysql基线检查
版本加固 选择稳定版本并及时更新、打补丁。 稳定版本:发行6-12个月以内的偶数版本。 检查方法: 使用sql语句:select version(); 检查结果: 存在问题:当前数据库版本较老需要更新 解决方案:前往http://www.mysql…...
跨境电商卖家敦煌、雅虎、乐天、亚马逊测评自养号的重要性!
作为亚马逊、敦煌、乐天、雅虎等跨境的卖家,这两年以来,面对流量越来越贵的现实,卖家需要更加珍惜每次访问listing页面的流量,把转化做好,把流量尽可能转化为更多的订单。 提升转化率的技巧 提升产品转化率࿰…...
Python 之 Matplotlib xticks 的再次说明、图形样式和子图
文章目录一. 改变 x 轴显示内容 xticks 方法再次说明1. x 轴是数值型数据2. 将 x 轴更改为字符串3. 总结二. 其他元素可视性1. 显示网格:plt.grid()2. plt.gca( ) 对坐标轴的操作三. plt.rcParams 设置画图的分辨率,大小等信息四. 图表的样式参数设置1. …...
3.InfluxDB WEB使用
结合telegraf做指标数据收集 点击 Load Data -> Telegraf 配置界面 influxDB支持在WEB-UI中生成配置文件 然后利用telegraf通过远程URL请求的方式进行获取 点击CREATE CONFIGURATION 创建telegraf配置文件 选择Bucket InfluxDB提供了很多配置好的监控模板供用户选择 可以…...
git冲突合并
一、版本说明 dev:本地仓库中的dev分支 master:本地仓库中的master分支 remotes/origin/master和origin/master:都是远程仓库上的master分支 二、一个解决冲突的常规流程 1、前提条件:不能在master分支上修改任何文件。master分支…...
项目自动化构建工具make/Makefile
目录 make/Makefile概念和关系 make/Makefie的使用 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重…...
双目客流统计方案的应用原理
双目客流统计客流摄像头采用立体视觉技术实现高度统计功能。基于视差原理。利用双镜头摄取的两幅图像的视差,构建三维场景,在检测到运动目标后。通过计算图像对应点间的位置偏差。获取目标的三维信息,在深度图像中对目标的检测与追踪…...
python魔术方法(二)
__getattr__() class A:def __getattr__(self,name):print(f"getting {name}")raise AttributeErroro A() print(o.test)程序调用一个对象的属性,当这个属性不存的时候希望程序做些什么,这里我们打印希望的属性,并且抛出异常 __…...
cmd for命令笔记
语法 help for输出如下: 对一组文件中的每一个文件执行某个特定命令。 FOR %variable IN (set) DO command [command-parameters] %variable 指定一个单一字母可替换的参数。 (set) 指定一个或一组文件。可以使用通配符。 command 指定对每个文件执行的命令。 c…...
4.1 Filter-policy
1. 实验目的 熟悉Filter-policy的应用场景掌握Filter-policy的配置方法2. 实验拓扑 Filter-policy实验拓扑如图4-5所示: 图4-5:Filter-policy 3. 实验步骤 (1) 网络连通性 R1的配置 <Huawei>system-vi…...
day15_常用类
今日内容 上课同步视频:CuteN饕餮的个人空间_哔哩哔哩_bilibili 同步笔记沐沐霸的博客_CSDN博客-Java2301 零、 复习昨日 一、作业 二、代码块[了解] 三、API 四、Object 五、包装类 六、数学和随机 零、 复习昨日 抽象接口修饰符abstractinterface是不是类类接口属性正常属性没…...
【网络原理5】IP协议篇
目录 IP协议报头 4位版本号 4位首部长度 8位服务类型(TOS) 16位总长度 IP拆包 16位标识、3位标志、13位片偏移编辑 8位生存时间(TTL) 8位协议 16位首部校验和 网络地址管理 32位源ip&32位目的ip 方案一:动态分配ip地址 方案2:NAT网络地址转换(使用一个ip代…...
Unity导出WebGL工程,并部署本地web服务器
WebGL打包 设置修改 在Build Settings->PlayerSettings->Other Settings->Rendering 将Color Space 设置为Gamma 将Lightmap Encoding 设置为NormalQuality 在Build Settings->PlayerSettings->Publishing Settings 勾选Decompression Fallback 打包 完成配…...
蓝桥杯考试总结汇总
一进考场设置devc快捷键 设置注释和取消注释快捷键设置代码自动补全快捷键开启devc调试功能,详细可以看怎么开调试功能https://blog.csdn.net/hz18790581821/article/details/78418648比赛过程中,如果不相信自己是否做对,没有把握的…...
MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
【Linux】Linux 系统默认的目录及作用说明
博主介绍:✌全网粉丝23W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
