【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比赛过程中,如果不相信自己是否做对,没有把握的…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...
有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
Unity UGUI Button事件流程
场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...
数据结构第5章:树和二叉树完全指南(自整理详细图文笔记)
名人说:莫道桑榆晚,为霞尚满天。——刘禹锡(刘梦得,诗豪) 原创笔记:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 上一篇:《数据结构第4章 数组和广义表》…...
【实施指南】Android客户端HTTPS双向认证实施指南
🔐 一、所需准备材料 证书文件(6类核心文件) 类型 格式 作用 Android端要求 CA根证书 .crt/.pem 验证服务器/客户端证书合法性 需预置到Android信任库 服务器证书 .crt 服务器身份证明 客户端需持有以验证服务器 客户端证书 .crt 客户端身份…...
Mysql故障排插与环境优化
前置知识点 最上层是一些客户端和连接服务,包含本 sock 通信和大多数jiyukehuduan/服务端工具实现的TCP/IP通信。主要完成一些简介处理、授权认证、及相关的安全方案等。在该层上引入了线程池的概念,为通过安全认证接入的客户端提供线程。同样在该层上可…...
