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

2023-05-16

目录

  • 标准函数
    • 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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Kotlin】Kotlin函数那么多,你会几个? 的相关文章

随机推荐

  • Jupyter Notebook安装

    Jupyter Notebook是一个非常好用的交互式Python运行的软件 安装方法如下 在命令行输入 pip3 install jupyter 安装后根据提示 xff0c Jupyter相关软件安装在 local bin目录下 xff0
  • Ubuntu添加截屏快捷键的方法

    在Ubuntu下面具有截屏的命令 xff08 gnome screenshot xff09 xff0c 可以通过简单的设置方便的添加截屏快捷键 通过 Settings gt Devices gt Keyboard选项 xff0c 添加快捷键
  • Windows下修改Jupyter Notebook默认字体的方法(custom.css)

    在Windows下Jupyter Notebook代码显示的默认字体为宋体 xff0c 视觉效果不是很好 xff0c 可以通过设置修改默认的显示字体 通过用户目录 C User Administrator jupyter custom 下的
  • Jupyter Notebook添加代码自动补全功能的方法

    Jupyter Notebook成为一款非常受欢迎的交互式Python运行环境的软件 通过如下的方法可以添加代码自动补全的功能 输入命令安装插件 pip3 install jupyter contrib nbextensions 然后运行
  • 修改grub默认启动选项的方法

    在Windows系统基础上 xff0c 再安装Linux xff0c 形成双系统 这样在grub启动菜单中会包含Linux Windows等多个选项 xff0c 默认为第一个选项 xff0c 常规的Linux启动 通过修改配置文件 etc
  • 在云服务器上搭建Jupyter Notebook服务

    Jupyter Notebook提供了远程登录的功能 xff0c 可以在云服务器上配置Jupyter Notebook xff0c 用户可以远程登录和运行Python代码 这里使用的是腾讯云的Ubuntu服务器 xff0c 配置方法如下 1
  • 常用Linux命令

    记录一些常用的Linux命令 1 用户管理 增加用户 useradd lt user name gt useradd g lt group name gt lt user name gt g选项指定新用户所属的用户组 修改用户的组别 use
  • 在云服务器上安装VNC远程桌面服务

    云服务器操作系统通常不包含图形界面 xff0c 通过在服务器上安装VNC服务 xff0c 可以让用户以图形化界面远程登录到云服务器 这里服务器使用的是Ubuntu Server 18 04系统 1 安装图形界面 首先在服务器端安装图形化桌面
  • 【Android】ADB无线连接Android设备

    目录 简介无线连接的条件adb连接设备方法一方法二 修改端口号方法一方法二 辅助工具android toolscrcpy gui 问题集合 简介 Android Debug Bridge xff0c 简称adb xff0c 是一种功能多样的
  • 人工智能学习:载入MNIST数据集(1)

    MNIST数据集是人工智能学习入门的数据集 xff0c 包含了一系列的手写的数字图片 载入MNIST数据集的方法很简单 xff0c Tensorflow集成了载入数据集的方法 首先导入tensorflow模块和matplotlib pypl
  • 人工智能学习:MNIST数据分类识别神经网络(2)

    在MNIST数据集上构建一个神经网络 xff0c 进行训练 xff0c 以达到良好的识别效果 1 导入模块 首先 xff0c 导入必要的模块 span class token keyword import span numpy span c
  • 人工智能学习:NMIST数据分类识别-CNN网络(3)

    这里采用CNN模型 xff08 卷积神经网络 xff09 来进行MNIST数据集的分类识别 1 导入模块 首先 xff0c 导入需要的模块 span class token keyword import span numpy span cl
  • 人工智能学习:CIFAR-10数据分类识别(4)

    与MNIST类似 xff0c CIFAR 10同样是人工智能学习入门的数据集之一 xff0c 它包含飞机 汽车 小鸟等10个类别的图片 xff0c 一共60000张图片 xff0c 其中训练集占50000张 xff0c 测试集占10000张
  • 人工智能学习:CIFAR-10数据分类识别-VGG网络(5)

    这里尝试采用VGG网络对CIFAR 10数据集进行分类识别 1 导入需要的模块 span class token keyword import span numpy span class token keyword as span np s
  • 人工智能学习:PASCAL VOC数据集读取(6)

    PASCAL VOC是一个国际的计算机视觉挑战赛 xff0c 数据集包含了20个分类的3万多张图片 挑战赛及其数据集基础上涌现不少知名的目标检测模型如R CNN xff0c YOLO xff0c SSD等 可以通过下载和读取的方法载入PAS
  • 人工智能学习:Microsoft COCO数据集读取(7)

    Microsoft COCO xff08 Common Objects in Context xff09 是微软研发维护的一个大型的数据集 包含了30多万张图片和91个目标分类 可用于目标识别 xff08 Object Detection
  • 人工智能学习:ResNet神经网络(8)

    ResNet是一种非常有效的图像分类识别的模型 xff0c 可以参考如下的链接 https blog csdn net qq 45649076 article details 120494328 ResNet网络由残差 xff08 Resi
  • 人工智能学习:倒立摆(CartPole)(9)

    倒立摆是强化学习的一个经典模拟对象 xff0c 通过对倒立摆对象的持续的动作输入 xff0c 使倒立摆保持在竖立的状态或者倒下 Python提供了一个模拟库 xff08 gym xff09 来模拟倒立摆等一些典型的难度控制对象 首先载入gy
  • 理解卡尔曼滤波

    卡尔曼滤波被广泛的应用于运动估计中 xff0c 在飞行器中多有应用 xff0c 区别于普通滤波如低通滤波 xff0c 卡尔曼滤波具有不延时的优点 xff0c 即普通的低通滤波在过滤噪声的同时也会引入信号滞后 xff0c 而卡尔曼滤波则可以实
  • 【Kotlin】Kotlin函数那么多,你会几个?

    目录 标准函数letrunwithapplyalsotakeIftakeUnlessrepeat小结作用域函数的区别作用域函数使用场景 简化函数尾递归函数 xff08 tailrec xff09 扩展函数高阶函数内联函数 xff08 inl