Kotlin 扩展函数与内联函数

news/2025/2/15 4:35:12/

Kotlin扩展函数

Kotlin 的扩展函数是 Kotlin 中非常强大且实用的功能。它允许你为现有的类添加新的方法,而不需要修改其源代码。这意味着你可以在已有的类上“扩展”新的功能,使用起来就像是原本就存在这些方法一样。

扩展函数的基本语法

kotlin">fun 类名.方法名(参数列表): 返回类型 {// 函数体
}

我们去看看Kotlin官方为我们实现的扩展函数例子,就拿官方对Float的扩展方法pow()举例,Float类本身并没有实现乘方功能的方法,在没有这个方法之前,我们只能使用Math类的静态方法进行乘方运算:

kotlin">Math.pow(2.0, 2.0

当我们在Kotlin中这样写,编译器会提示我们:
在这里插入图片描述
这里告诉我们我们可以用Kotlin的函数如下:

2.0.pow(2.0)//pow函数的实现
public actual inline fun Double.pow(x: Double): Double = nativeMath.pow(this, x)

我们看到这个pow函数是Kotlin对Double类的扩展。我们也可以自己多现有的类进行扩展,例如我们需要打印字符串的最后一个字符,我们可以对String类进行扩展:

fun String.printLastChar() {println(this[length - 1])
}fun main() {//对扩展函数的调用"Hello".printLastChar()//取函数的引用使用invoke进行调用String::printLastChar.invoke("Hello")//直接调用函数引用(String::printLastChar)("Hello")
}

扩展函数的作用域

扩展函数可以定义在Kotlin文件中,是其不属于任何一个类,其他地方都可以使用,类似于上面的String.printLastChar()方法;另外一种是可以声明在类中:

class ExtensionsSample{fun normalFun(){"Java".printLastChar()"Kotlin".printFirstChar()}private fun String.printFirstChar() {println(this[0])}
}

如果扩展函数定义在某个类中,而定义在类中,printFirstChar()方法即是ExtensionsSample类的成员函数,同时又是String类的扩展函数,String类是这个扩展函数的Receiver,定义在类中的扩展函数的作用范围是类及其内部类。在kotlin中也无法通过对象调用定义在其内部的扩展方法,即是限定符是默认的public

扩展函数的原理

我们对扩展函数所在的文件进行反编译,看其生成的字节码文件反编译的结果如下:

public final void printFirstChar(@NotNull String $this$printFirstChar) {Intrinsics.checkNotNullParameter($this$printFirstChar, "$this$printFirstChar");char var2 = $this$printFirstChar.charAt(0);System.out.println(var2);
}

扩展函数并不是修改原有类的实际代码,它是在编译时通过生成额外的代码来实现的。虽然你能通过扩展函数像访问原有方法一样调用新方法,但它们实际上并没有改变原有类的行为。扩展函数只是通过静态解析(编译时确定类型)来实现的。

因此,扩展函数无法真正覆盖或修改类中的已有方法,也不能访问类的私有成员。

Kotlin扩展属性

Kotlin 还支持扩展属性,它可以为类添加属性,但只能是可读属性(即没有 setter 方法),因为扩展属性不能存储实际的数据。

val 类名.属性名: 类型get() = // 计算属性的值
val String.lastChar: Charget() = this[this.length - 1]fun main() {println("HelloWorld".lastChar)
}

在某些情况下,我们不需要扩展方法,只需要扩展属性即可。

inline

Kotlin官方文档在介绍inline关键字中写道使用不当是会有一定的性能问题的,官方的原话是这样介绍:
Using higher-order functions imposes certain runtime penalties: each function is an object, and it captures a closure. A closure is a scope of variables that can be accessed in the body of the function. Memory allocations (both for function objects and classes) and virtual calls introduce runtime overhead.
使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,并且会捕获一个闭包。 闭包那些在函数体内会访问到的变量的作用域。 内存分配(对于函数对象和类)和虚拟调用会引入运行时间开销。但是在许多情况下通过内联化 lambda 表达式可以消除这类的开销。

  • 使用 inline 修饰的函数,会在调用点处将函数的代码“展开”(也叫内联),而不是生成一个实际的函数调用。这意味着函数体的代码会直接插入到调用它的地方,这避免了函数调用所带来的栈帧创建和返回的性能开销。
  • 高阶函数指的是接受另一个函数作为参数的函数。在没有 inline 时,传入的函数会在堆栈上创建一个对象,并通过引用进行调用,这会导致额外的内存分配和垃圾回收。使用 inline 后,传入的函数会直接嵌入到调用点,避免了创建和传递函数对象的开销。
  • 由于内联函数的代码直接插入到调用的地方,Kotlin 会避免为这些高阶函数(例如 lambda 表达式)分配对象,从而减少了内存的消耗。

如果一个高阶函数在循环中被调用,或者在Android中的onDraw方法中调用,高阶函数在执行的过程中就会创建临时对象,短时间创建大量对象,并且这些对象的生命周期都很短,就会造成内存抖动

fun main() {highOrder(5) {println(it)it}
}fun highOrder(number: Int, callBack: (Int) -> Int) {callBack(number + 1)
}

我们对上面的文件进行反编译可以发现确实是在执行的时候是通过Function1
这个类来实现我们的高阶函数传入的函数

public static final void main() {highOrder(5, (Function1)null.INSTANCE);
}// $FF: synthetic method
public static void main(String[] var0) {main();
}public static final void highOrder(int number, @NotNull Function1 callBack) {Intrinsics.checkNotNullParameter(callBack, "callBack");callBack.invoke(number + 1);
}

而我们给高阶函数添加highOrder前面添加关键字inline之后,让他变成内联函数在经过反编译可以看到:

public static final void main() {int number$iv = 5;int $i$f$highOrder = false;int it = number$iv + 1;int var3 = false;System.out.println(it);
}

而这里就是直接把我们传入的lambda表达式具体的执行逻辑直接放到了这里,由此可见inline关键字的主要作用就是减少调用栈的深度,并能够优化高阶函数在执行时的性能问题。

noinline

如果不希望内联所有传给内联函数的 lambda 表达式参数都内联,那么可以用 noinline 修饰符标记不希望内联的函数参数:

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { …… }

假设有一种场景,你的高阶函数传入了两个函数参数,你希望其中的一个函数参数以函数引用的方式作为返回值,就可以使用到noinline关键字:

inline fun foo(callBack: (Int) -> Int, noinline result: () -> Unit): () -> Unit {callBack(2)return result
}

noinline的作用就是局部的、有指向性的关闭Kotlin的内联优化。

crossinline

我们先来看一段代码:

fun main() {println("This is before of heightFunc")heightFunc {return@heightFunc}println("This is after of heightFunc")
}fun heightFunc(call: () -> Unit) {call()println("This is inner of heightFunc")
}

这样写所有的打印的地方都会执行,而使用inline关键字标记heightFunc函数之后,并且去掉return的标签,那么结果就发生了改变

fun main() {println("This is before of heightFunc")heightFunc {return}println("This is after of heightFunc")
}fun heightFunc(call: () -> Unit) {call()println("This is inner of heightFunc")
}

最终的执行结果为只打印了This is before of heightFunc,我们通过内联函数的优化可以想到其中的原因。
当我们需要在内联函数中间接的调用传入的lambda表达式时,编译器会给我们如下提示:
在这里插入图片描述
而我们在外面调用内联函数,在lambda表达式中使用return的时候,是不被允许的,要使用必须加标签显示的指定退出那个函数
在这里插入图片描述
所以crossinline的作用就是如果你希望禁止 lambda 中的 return 语句影响外部函数的执行,可以使用 crossinline。这块lambda中的return和内联函数结合起来使用具体得多写,编译器会给出提示。


http://www.ppmy.cn/news/1572149.html

相关文章

一区IEEE Trans|特征模态分解FMD,一维数据分解新方法-matlab免费代码

引言 本期介绍一款小众、最新、性能强大的一维数据分解方法,特征模态分解Feature Mode Decomposition,FMD),2023年发表在中科院1区top sci期刊IEEE Transactions on Industrial Electronics (简称IEEE TIE)&#xff0…

Git的常用命令及常见问题处理方法

目录 一、介绍二、常用 Git 命令1. 配置用户信息2. 初始化仓库3. 克隆远程仓库4. 查看状态5. 添加文件到暂存区6. 提交更改7. 查看提交历史8. 查看文件差异9. 查看分支10. 切换分支11. 合并分支12. 处理冲突13. 远程操作14. 标签管理15. 撤销操作 三、常见问题处理方法1. 无法推…

排序算法大合集

排序算法大合集 翻了翻很久以前写的算法报告,现在整理一下。 由难度从简单到难排序。 桶排序、冒泡排序、选择排序、快速排序。 最简单粗暴——桶排序 (一)桶排序原理 桶排序,是一个目前速度最快的一种排序 基本思想是将无序列数依次装进一个按元素名命名数组中 最后…

【Linux】【网络】IO多路复用 select、poll、epoll

【Linux】【网络】IO多路复用 select、poll、epoll IO 多路复用 进程或线程同时监控多个文件描述符,查看描述符上是否有事件发生,从而提高资源利用率和系统吞吐量。 1. select int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exc…

【JavaScript爬虫记录】记录一下使用JavaScript爬取m4s流视频过程(内含ffmpeg合并)

前言 前段时间发现了一个很喜欢的视频,可惜网站不让下载,简单看了一下视频是被切片成m4s格式的流文件,初步想法是将所有的流文件下载下来然后使用ffmpeg合并成一个完整的mp4,于是写了一段脚本来实现一下,电脑没有配python环境,所以使用JavaScript实现,合并功能需要安装ffmpeg,…

算法-整理图书,反转链表数据返回

力扣题目:LCR 123. 图书整理 I - 力扣(LeetCode) 书店店员有一张链表形式的书单,每个节点代表一本书,节点中的值表示书的编号。为更方便整理书架,店员需要将书单倒过来排列,就可以从最后一本书…

Go语言的内存分配原理

Go语言的内存分配原理 Go语言的内存管理分为两个主要区域:栈(Stack) 和 堆(Heap)。理解这两个区域的工作原理,可以帮助你写出更高效的代码,并避免一些常见的性能问题。 1. 栈(Stac…

《通过DINO语义引导进行可变形单次人脸风格化》学习笔记

paper:2403.00459 GitHub:zichongc/DoesFS: [CVPR 24] Official repository for Deformable One-shot Face Stylization via DINO Semantic Guidance 目录 摘要 1、介绍 2、相关工作 2.1 人脸风格化 2.2 ViT特征表示 3、方法 3.1 预备知识 3.2 框架 3.3 …