Kotlin 扩展函数与内联函数

news2025/2/23 16:44:28

Kotlin扩展函数

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

扩展函数的基本语法

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

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

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: Char
    get() = 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.coloradmin.cn/o/2298184.html

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

相关文章

企业文件防泄密软件哪个好?

在企业文件防泄密软件领域,天锐绿盾和中科数安都是备受认可的品牌,它们各自具有独特的特点和优势。 以下是对这两款软件的详细比较: 天锐绿盾 功能特点 集成性强:集成了文件加密、数据泄露防护DLP、终端安全管理、行为审计等数据安…

【Qt 常用控件】多元素控件(QListWidget、QTableWidgt、QTreeWidget)

**View和**Widget的区别? **View的实现更底层,**Widget是基于**View封装实现的更易用的类型。 **View使用MVC结构 MVC是软件开发中 经典的 软件结构 组织形式,软件设计模式。 M(model)模型。管理应用程序的核心数据和…

VS2022中.Net Api + Vue 从创建到发布到IIS

VS2022中.Net Api Vue 从创建到发布到IIS 前言一、先决条件二、创建项目三、运行项目四、增加API五、发布到IIS六、设置Vue的发布 前言 最近从VS2019 升级到了VS2022,终于可以使用官方的.Net Vue 组合了,但是使用过程中还是有很多问题,这里记录一下. 一、先决条件 Visual …

Windows 11 搭建私有知识库(docker、dify、deepseek、ollama)

一、操作系统信息 版本 Windows 11 家庭中文版 版本号 23H2 安装日期 ‎2023/‎8/‎21 操作系统版本 22631.4460二、搭建思路 ollama拉取deepseek、bge-m3模型docker拉取dify的镜像dify链接ollama使用模型,并上传文件搭建知识库,创建应用 三、搭建步骤…

安装OpenJDK21(linux、macos)

文章目录 安装OpenJDK21java21linux下安装配置mac下安装 安装OpenJDK21 java21 封神!Java 21正式发布了,迎来了史诗级新特性,堪称版本最强!!! 视频链接:https://www.bilibili.com/video/BV1E8…

变分边界详解

起因 当时看VAE论文时有这么一段,但是看完直接一头雾水,这都那跟哪,第一个公式咋做的变换就变出那么一堆。网上搜了很多博客都语焉不详,只好自己来写一篇,希望能解答后来人的疑惑。 公式1 参考文章:证据…

Next.js 15【实用教程】2025最新版

官网 https://nextjs.org/docs/app/getting-started Next.js 简介 Next.js 由 Vercel 开发和维护,旨在解决单页应用(SPA)和多页应用(MPA)在性能和 SEO 上的不足。 核心特性 服务端渲染(SSR)--…

2025-02-13 学习记录--C/C++-PTA 7-17 爬动的蠕虫

一、题目描述 ⭐️ 二、代码&#xff08;C语言&#xff09;⭐️ #include <stdio.h>int main() {int N, U, D; // N: 井的总高度&#xff0c;U: 每分钟向上爬的高度&#xff0c;D: 每分钟滑下的高度int height 0; // 蠕虫当前的高度int minute 0; // 蠕虫爬行的时间sc…

Elasticsearch+Logstash+Kibana可视化集群部署

文章目录 1.组件介绍简述2.集群规划3.Es组件部署4.Logstash组件部署5.Kibana组件部署6.Kibana的基础使用 1.组件介绍简述 Elasticsearch&#xff1a;开源实时分布式搜索和分析引擎&#xff0c;支持大规模数据存储和高吞吐量&#xff0c;提供丰富的搜索功能和可扩展性。 Logsta…

DeepSeek+Excel 效率翻倍

2025年初&#xff0c;DeepSeek以惊人的效率突破技术壁垒&#xff0c;用极低的成本实现了与行业顶尖AI相媲美的性能&#xff0c;瞬间成为全球科技领域的热门话题。 那么AI工具的普及将如何改变我们的工作方式&#xff1f;Excel会被取代吗&#xff1f; 今天&#xff0c;珠珠带你…

将Sqlite3数据库挂在内存上处理

创作灵感&#xff1a;最近把小学生的口算题从2位数改到3位数&#xff0c;100以内四则运算练习&#xff08;千纬数学&#xff09;再次更新&#xff0c;选取难题-CSDN博客要不断刷题目&#xff0c;以前100以内的加减乘除也是这样刷出来的&#xff0c;代码如下&#xff1a; impor…

electron.vite 项目创建以及better-sqlite3数据库使用

1.安装electron.vite npm create quick-start/electronlatest中文官网&#xff1a;https://cn.electron-vite.org/ 2. 安装项目依赖 npm i3.修改 electron-builder 配置文件 appId: com.electron.app productName: text33 directories:buildResources: build files:- !**/.v…

C++,STL容器适配器,stack:栈深入解析

文章目录 一、容器概览与核心特性核心特性速览二、底层实现原理1. 容器适配器设计2. 默认容器对比三、核心操作详解1. 容器初始化2. 元素操作接口3. 自定义栈实现四、实战应用场景1. 括号匹配校验2. 浏览器历史记录管理五、性能优化策略1. 底层容器选择基准2. 内存预分配技巧六…

Vue笔记(十)

一、AI的基本认知 二、ChatGPT的基本使用 三、AI插件--Copilot入门 1.Copilot是由OpenAI和GitHub合作开发的AI编程辅助插件&#xff0c;基于大量代码训练&#xff0c;能根据上下文自动生成代码建议。 2.安装与配置&#xff1a;在常用代码编辑器&#xff08;如Visual Studio Cod…

Ubuntu下载安装Docker-Desktop

下载 Ubuntu | Docker Docs 预备工作 Ubuntu增加docker apt库-CSDN博客 安装 sudo apt-get updatesudo apt install gnome-terminal# sudo apt install -y docker-composesudo apt-get install ./docker-desktop-amd64.deb 测试 sudo docker run hello-worldHello from D…

DeepSeek 突然来袭,AI 大模型变革的危机与转机藏在哪?

随着人工智能技术的飞速发展&#xff0c;大模型领域不断涌现出具有创新性的成果。DeepSeek 的横空出世&#xff0c;为 AI 大模型领域带来了新的变革浪潮。本文将深入探讨 DeepSeek 出现后 AI 大模型面临的危机与转机。 冲冲冲&#xff01;&#xff01;&#xff01; 目录 一、…

C#运动控制——轴IO映射

1、IO映射的作用 该功能允许用户对专用 IO 信号的硬件输入接口进行任意配置&#xff0c;比如轴的急停信号&#xff0c;通过映射以后&#xff0c;可以将所有轴的急停信号映射到某一个IO输入口上&#xff0c;这样&#xff0c;我们只要让一个IO信号有效就可以触发所有轴的急停。 进…

ArrayList、LinkedList、HashMap、HashTable、HashSet、TreeSet

集合族谱 在这些集合中&#xff0c;仅有vector和hashtable是线程安全的&#xff0c;其内部方法基本都有synchronized修饰。 ArrayList 底层采用Object数组实现&#xff0c;实现了RandomAccess接口因此支持随机访问。插入删除操作效率慢。 ArrayList需要一份连续的内存空间。 A…

DeepSeek 指导手册(入门到精通)

第⼀章&#xff1a;准备篇&#xff08;三分钟上手&#xff09;1.1 三分钟创建你的 AI 伙伴1.2 认识你的 AI 控制台 第二章&#xff1a;基础对话篇&#xff08;像交朋友⼀样学交流&#xff09;2.1 有效提问的五个黄金法则2.2 新手必学魔法指令 第三章&#xff1a;效率飞跃篇&…

2024 CyberHost 语音+图像-视频

项目&#xff1a;CyberHost: Taming Audio-driven Avatar Diffusion Model with Region Codebook Attention 音频驱动的身体动画面临两个主要挑战&#xff1a;&#xff08;1&#xff09;关键人体部位&#xff0c;如面部和手部&#xff0c;在视频帧中所占比例较小&#x…