【Kotlin】Kotlin函数那么多,你会几个?

news2024/11/15 21:32:36

目录

  • 标准函数
    • 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.或直接使用对象值。返回值是对象本身。对于不返回值且主要对接收器对象的成员进行操作的代码块,请使用applyapply的常见情况是对象配置。

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表达式的对象调用这样的函数时,它会形成一个临时作用域。在此范围内,您可以访问不带名称的对象。此类函数称为作用域函数。作用域函数有五个:letrunwithapplyalso

作用域函数的区别

在这里插入图片描述

作用域函数使用场景

函数使用场景
let1、对非空对象执行lambda
2、在局部范围中引入表达式作为变量
with对对象的函数调用进行分组
run1、对象配置和计算结果
2、在需要表达式的地方运行语句
apply1、对象配置和计算结果
2、不返回值且主要对接收器对象的成员进行操作的代码块
also1、执行一些将上下文对象作为参数的操作。
2、需要引用对象而不是其属性和函数的操作
3、不希望从外部范围隐藏此引用时

简化函数

kotlin中,变量可以通过等号赋值,函数同样被允许使用等号进行赋值,这些使用等号赋值的函数就叫做简化函数。

简化函数的编写规范是有所要求的,如果函数表达式只有一行,才可以使用等号赋予函数其表达式。简化函数默认return函数的最后一行代码。

fun main(args: Array<String>) {
    println(test1())// result:2
    println(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: Int
    get() = 0

fun main(args: Array<String>) {
    var a = "aaaa000"
    print(a.lastIndex) // result:0
}

小知识

  • 扩展函数不可以重写。
  • 扩展函数实质上是静态函数。
  • 在扩展函数里使用this引用的是扩展函数的类型,而不是函数当前所在类。
  • 如果扩展函数在其接收者类型之外声明,则它不能访问接收者privateprotected成员。
  • 扩展函数、属性的代码优先级高于接收者类型原有的函数、对象,等同于重写接收者的同名函数、属性。

高阶函数

高阶函数是将函数作为参数或返回函数的函数。

在以下的示例代码中,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返回值,并将其print
    val 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:50

    val 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:-10

    val aaa = num1AndNum2(20, 30, lambda2) // result:50
    println("$aaa")

	val anonymous1 = num1AndNum2(20, 30, a) // result:-10
    val 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 表达式,都会创建一个新的匿名类实例,会造成额外的内存和性能开销,但这种开销可以通过inline lambda表达式来消除。使用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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/351773.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CUDA的统一内存

CUDA的统一内存 文章目录CUDA的统一内存N.1. Unified Memory IntroductionN.1.1. System RequirementsN.1.2. Simplifying GPU ProgrammingN.1.3. Data Migration and CoherencyN.1.4. GPU Memory OversubscriptionN.1.5. Multi-GPUN.1.6. System AllocatorN.1.7. Hardware Coh…

如何学习 Web3

在本文中&#xff0c;我将总结您可以采取的步骤来学习 Web3。从哪儿开始&#xff1f;当我们想要开始新事物时&#xff0c;我们需要一些指导&#xff0c;以免在一开始就卡住。但我们都是不同的&#xff0c;我们有不同的学习方式。这篇文章基于我学习 Web3 的非常个人的经验。路线…

SpringBoot集成WebSocket实现客户端与服务端长连接通信

场景&#xff1a; WebSocket协议是用于前后端长连接交互的技术&#xff0c;此技术多用于交互不断开的场景。特点是连接不间断、更轻量&#xff0c;只有在关闭浏览器窗口、或者关闭浏览器、或主动close&#xff0c;当前会话对象才会关闭。 这里只是简单的记录一下使用方式 一、服…

Proxy lab

CSAPP Proxy Lab 本实验需要实现一个web代理服务器&#xff0c;实现逐步从迭代到并发&#xff0c;到最终的具有缓存功能的并发代理服务器。 Web 代理是充当 Web 浏览器和终端服务器之间的中间人的程序。浏览器不是直接联系终端服务器获取网页&#xff0c;而是联系代理&#x…

关系型数据库的三大范式

一、简而言之 1、是什么&#xff1f; 三大范式是针对关系型数据库的一种数据库设计规范&#xff0c;使数据库设计符合约定的规范要求。 2、为什么要符合该规范&#xff1f; 为了建立冗余较小、结构合理的数据库。 3、三大范式内容的简单理解&#xff08;Normal Form&#…

2023美赛D题:可持续发展目标

以下内容全部来自人工翻译&#xff0c;仅供参考。 文章目录背景要求术语表文献服务背景 联合国制定了17个可持续发展目标&#xff08;SDGs&#xff09;。实现这些目标最终将改善世界上许多人的生活。这些目标并不相互独立&#xff0c;因此&#xff0c;一些目标的积极进展常常…

2023美国大学生数学建模竞赛选题建议

总的来说&#xff0c;这次算是美赛环境题元年&#xff0c;以往没有这么多环境题目&#xff0c;大部分题目都是开放度相当高的题目。C君认为的难度&#xff1a;D>C>AE>BF&#xff0c;开放度&#xff1a;DF>ABE>C。A题 遭受旱灾的植物群落这次A题为环境类题目&…

【技术分享】在RK3568上如何烧录MAC

本次我们使用的是触觉智能基于RK3568研发的IDO-EVB3568来给大家演示如何烧录MAC。 这款开发板拥有四核A55&#xff0c;主频高达2.0G&#xff0c;支持高达8GB高速LPDDR4&#xff0c;1T算力NPU &#xff0c;4K H.265硬解码&#xff0c;4K HDMI2.0显示输出&#xff0c;支持双通…

AMEPD SSD1680 调试记录

AMEPD Active Martix Electrophoretic Display&#xff0c;有源矩阵电泳显示屏。就是电纸书那种屏&#xff0c;调试效果使用感受和我的Kindle差不多。屏幕参数屏幕 IC为SSD1680122*250&#xff0c;单bit控制&#xff0c;1为白&#xff0c;0为黑逐行刷新&#xff0c;一个字节8bi…

JavaScript 浏览器中执行

本章节为大家介绍如何在浏览器上进行 JavaScript 代码的运行与调试。目前的主流浏览器有谷歌的Chrome&#xff08;使用blink内核&#xff09;&#xff0c;微软的edge&#xff08;使用chromium内核&#xff0c;这是一款谷歌提供的开源浏览器内核&#xff09;和IE&#xff08;使用…

记录锁,间隙锁,插入意向锁,临键锁兼容关系

插入意向锁是什么&#xff1f; 注意&#xff01;插入意向锁名字里虽然有意向锁这三个字&#xff0c;但是它并不是意向锁&#xff0c;它属于行级锁&#xff0c;是一种特殊的间隙锁。 在MySQL的官方文档中有以下重要描述&#xff1a; An Insert intention lock is a type of gap…

羊了个羊游戏开发教程3:卡牌拾取和消除

本文首发于微信公众号&#xff1a; 小蚂蚁教你做游戏。欢迎关注领取更多学习做游戏的原创教程资料&#xff0c;每天学点儿游戏开发知识。嗨&#xff01;大家好&#xff0c;我是小蚂蚁。终于要写第三篇教程了&#xff0c;中间拖的时间有点儿长&#xff0c;以至于我的好几位学员等…

2023美赛C题思路数据代码分享

文章目录赛题思路2023年美国大学生数学建模竞赛选题&论文一、关于选题二、关于论文格式三、关于论文提交四、论文提交流程注意不要手滑美赛C题思路数据代码【最新】赛题思路 (赛题出来以后第一时间在CSDN分享) 最新进度在文章最下方卡片&#xff0c;加入获取一手资源 202…

【小西】同步咪咕订单给咪咕方(写接口给第三方)

同步咪咕订单给咪咕方前言思路实现1、定义请求体和响应信息MiGuOrderSyncReqMiGuOrderSyncResp2、nacos定义好咪咕相关配置信息3、同步咪咕参数配置4、MiGuOrderSyncControl5、MiGuOrderSyncService6、MiGuOrderSyncServiceImplCreateAscIISignUtil 生成参数 字典排序 签名Hmac…

数据分析:消费者数据分析

数据分析&#xff1a;消费者数据分析 作者&#xff1a;AOAIYI 创作不易&#xff0c;如果觉得文章不错或能帮助到你学习&#xff0c;记得点赞收藏评论一下哦 文章目录数据分析&#xff1a;消费者数据分析一、前言二、数据准备三、数据预处理四、个体消费者分析五、用户消费行为总…

【CMake】CMake构建C++代码(一)

在Linux开发过程中&#xff0c;难免会用到CMake来构建你的代码。本文将说明如何构建自己的代码&#xff0c;将自己的代码变为共享库&#xff0c;共其他代码使用。 文章目录在Linux开发过程中&#xff0c;难免会用到CMake来构建你的代码。本文将说明如何构建自己的代码&#xff…

R语言Ternary包绘制三元图、RGB三色空间分布图的方法

本文介绍基于R语言中的Ternary包&#xff0c;绘制三元图&#xff08;Ternary Plot&#xff09;的详细方法&#xff1b;其中&#xff0c;我们就以RGB三色分布图为例来具体介绍。 三元图可以从三个不同的角度反应数据的特征&#xff0c;因此在很多领域都得以广泛应用&#xff1b;…

2023美赛F题思路数据代码分享

文章目录赛题思路2023年美国大学生数学建模竞赛选题&论文一、关于选题二、关于论文格式三、关于论文提交四、论文提交流程注意不要手滑美赛F题思路数据代码【最新】赛题思路 (赛题出来以后第一时间在CSDN分享) 最新进度在文章最下方卡片&#xff0c;加入获取一手资源 202…

MySQL 索引 (只要能看完)(一篇就够了)

文章目录前言一、MySQL索引介绍1.1 索引的类别1.2 索引的创建原则二、索引的管理和使用2.1 制造实验数据2.2 explain 使用说明2.3 创建索引2.3.1 基于创建表时建立索引2.3.2 基于已创建好的表创建索引2.4 删除索引2.5 聚集索引和二级索引2.5.1 聚集索引2.5.2 二级索引&#xff…

【python知识】win10下如何用python将网页转成pdf文件

一、说明 本篇记录一个自己享用的简单工具。在大量阅读网上文章中&#xff0c;常常遇到一个专题对应多篇文章&#xff0c;用浏览器的收藏根本不够。能否见到一篇文章具有搜藏价值&#xff0c;就转到线下&#xff0c;以备日后慢慢消化吸收。这里终于找到一个办法&#xff0c;将在…