Kotlin 中的内联函数

1 inline

内联函数:消除 Lambda 带来的运行时开销。

举例来说:

fun main() {val num1 = 100val num2 = 80val result = num1AndNum2(num1, num2) { n1, n2 ->n1 + n2}
}fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {val result = operation(num1, num2)return result
}

在上面的代码中调用了 num1AndNum2() 函数,并通过 Lambda 表达式指定对传入的两个整型参数进行求和。这段代码在 Kotlin 中非常好理解,因为这是高阶函数最基本的用法。但是,Kotlin 代码最终还是要编译成 Java 字节码的,但是 Java 中没有高阶函数的概念。

将上述的 Kotlin 代码转换成 Java 代码:

public final class TestKt {public static final void main() {int num1 = 100;int num2 = 80;num1AndNum2(num1, num2, (Function2)null.INSTANCE); // 1}// $FF: synthetic methodpublic static void main(String[] var0) {main();}public static final int num1AndNum2(int num1, int num2, @NotNull Function2 operation) {Intrinsics.checkNotNullParameter(operation, "operation");int result = ((Number)operation.invoke(num1, num2)).intValue(); // 2return result;}
}

在注释 1 中可以看到 num1AndNum2 函数的第三个参数变成了一个 Function2 接口,这是一种 Kotlin 内置的接口,里面有一个待实现的 invoke 函数(注释 2),并把 num1 和 num2 传了进去。这样,在调用 num1AndNum2 函数的时候,之前的 Lambda 表达式在这里变成了 Function 接口的匿名类实现,然后在 invoke 函数中实现了 n1 + n2 的逻辑,并将结果返回。

这就是 Kotlin 高阶函数背后的实现原理:Lambda 表达式在底层被转换成了匿名类的实现方式。这表明,我们每调用一次 Lambda 表达式,就会创建一个新的匿名类实例,也就造成了额外的内存和性能开销。

内联函数就是用来消除 Lambda 表达式所带来的运行时开销。

内联函数的实现非常简单,只要在定义高阶函数是加上 inline 关键字的声明即可。 如下所示:

inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {val result = operation(num1, num2)return result
}

内联函数的工作原理其实并不复杂,Kotlin 编译器会将内联函数中的代码在编译的时候自动替换到调用它的地方,这样也就不存在运行时开销了。

以下是反编译的 Java 代码:

public final class TestKt {public static final void main() {int num1 = 100;int num2 = 80;int $i$f$num1AndNum2 = false; int var6 = false;int result$iv = num1 + num2; // 1}// $FF: synthetic methodpublic static void main(String[] var0) {main();}public static final int num1AndNum2(int num1, int num2, @NotNull Function2 operation) {int $i$f$num1AndNum2 = 0;Intrinsics.checkNotNullParameter(operation, "operation");int result = ((Number)operation.invoke(num1, num2)).intValue();return result;}
}

从注释 1 处可以看出是将内联函数中的全部代码替换到了函数调用处,也正因为此,内联函数才能完全消除 Lambda 表达式所带来的运行时开销。

2 noinline

比如,一个高阶函数接收了两个或者更多的函数类型的参数,这个时候就需要给这些函数类型的参数加上 inline 关键字。但是,如果我们只想内联其中的一个函数该怎么办呢?这个时候就用到 noinline 关键字了,如下所示:

inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit) {  
}

可以看到,原本 block1 和 block2 这两个函数类型的参数所引用的 Lambda 表达式都会被内联。但是,我们在 block2 参数前面又加上了 noinline 关键字,那么现在就只会对 block1 参数所引用的 Lambda 表达式进行内联了。这就是 noinline 关键字的作用。

那么,既然内联函数可以消除 Lambda 带来的运行时开销,为什么还要提供 noinline 关键字来排除内联功能呢?

这是因为,内联的函数类型参数在编译时会被代码替换,因此,它是没有真正的参数属性的。非内联的函数类型参数可以自由地传递给其他的任何函数,因为它是一个真实的参数,而且保留原有函数的特性,而内联函数的类型参数只允许传递给另外的一个内联函数,这就是它最大的局限性。

内联函数和非内联函数还有一个重要的区别:内联函数所引用的 Lambda 表达式中是可以使用 return 关键字来进行函数返回的,而非内联函数只能进行局部返回。

fun main() {println("main start") // 1val str = ""printString(str) { s ->println("lambda start") // 3if (s.isEmpty()) return@printString // 局部返回println(s)println("lambda end")}println("main end") // 5
}// 普通函数
fun printString(str: String, block: (String) -> Unit) {println("printString begin") // 2block(str)println("printString end") // 4
}// main start
// printString begin
// lambda start
// printString end
// main end

在这里定义了一个叫做 printString 的高阶函数,用于在 Lambda 表达式中打印传入的字符串参数。但是如果字符串参数为空,那么就不进行打印。注意,Lambda 表达式中是不允许直接使用 return 关键字的,这里使用了 return@printString 的写法,表示进行局部返回,并且不再执行 Lambda 表达式的剩余部分。

如果将 printString 函数声明称一个内联函数:

fun main() {println("main start") // 1val str = ""printString(str) { s ->println("lambda start") // 3if (s.isEmpty()) return // 可以直接使用 return 关键字println(s)println("lambda end")}println("main end")
}// 内联函数
inline fun printString(str: String, block: (String) -> Unit) {println("printString begin") // 2block(str)println("printString end")
}// main start
// printString begin
// lambda start

将 printString 函数声明为内联函数,就可以在 Lambda 表达式中使用 return 关键字了。此时的 return 代表的是返回外层的调用函数,也就 main() 函数。之所以会有这样的效果,是因为内联函数的代码替换。

3 corssinline

将高阶函数声明称内联函数是一种良好的习惯。事实上,绝大多数的高阶函数是可以直接声明成内联函数的,但是也有少部分例外的情况:
内联函数
在这里插入图片描述

我们首先在内联函数 runRunnable 中,创建了一个 Runnable 对象,并在 Runnable 的 Lambda 表达式中调用了传入的函数类型参数。而 Lambda 表达式在编译的时候会被转换成匿名内部类的实现方式,也就是说, 上述代码实际上是在匿名内部类中调用了传入的函数类型参数。

这是因为,内联函数的 Lambda 表达式中允许使用 return 关键字(也就是 block 函数中允许 return),和高阶函数的匿名内部类实现中不允许出现 return 关键字之间造成了冲突。

也就是说,如果我们在高阶函数中创建了另外的 Lambda 表达式或者匿名类的实现,并且在这些实现中也调用了函数类型的参数,此时再将高阶函数声明成内联函数,就一定提示错误。

那么是不是在这种情况下就无法使用内联函数了呢?也不是,借助 corssinline 关键字就可以很好的解决这个问题:

inline fun runRunnable(crossinline block: () -> Unit) {val runnable = Runnable {block()}runnable.run()
}

可以看到,在函数类型参数的前面加上 crossinline 声明,代码就可以正常编译通过了。

那么这个 crossinline 关键字是什么意思呢?crossinline 关键字就像是一个契约,用于保证内联函数的 Lambda 表达式中一定不会出现 return 关键字,这样就不存在冲突了。

声明了 crossinline 之后,我们就无法在调用 runRunnable 函数时的 Lambda 表达式中使用 return 关键字进行函数表达式返回了,但是仍然可以使用 return@runRunnable 的写法进行局部返回。总体来说,除了在 return 关键字的使用上有所区分外,crossinline 保留了内联函数的其他所有特性。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker 安装最新版本 Jenkins

目录 1、下载、启动容器、更新到最新版本 2、查看初始密码两种方式: 3、默认安装的部分未汉化,删除默认的汉化插件。重启容器,重新安装汉化插件 4、安装 Publish over SSH、docker-build-step 、Docker Commons 插件 5、配置服务器连接信…

【红帽战报】6月RHCE考试喜报!

往期战报回顾: 点击查看【战报】5月RHCE考试喜报!通过率100% 点击查看【战报】4月份红帽考试战报! 点击查看【战报】PASS!PASS!2023年终来一波RHCE考试 微思网络-红帽官方授权合作伙伴!面向全国招生&…

Python爬取中国天气网天气数据.

一、主题式网络爬虫设计方案 1.主题式网络爬虫名称 名称:Python爬取中国天气网天气数据 2.主题式网络爬虫爬取的内容与数据特征分析 本次爬虫主要爬取中国天气网天气数据 3.主题式网络爬虫设计方案概述(包括实现思路与技术难点) reques…

[火灾警报系统]yolov5_7.0-pyside6火焰烟雾识别源码

国内每年都会发生大大小小的火灾,造成生命、财产的损失。但是很多火灾如果能够早期发现,并及时提供灭火措施,将会大大较小损失。本套源码采用yolov5-7.0目标检测算法结合pyside6可视化界面源码,当检测到火灾时,能否发出…

第三阶段Spark

Spark和PySpark的介绍 PySpark的相关设置 安装PySpark库 pip install pyspark pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyspark 构建PySpark执行环境入口对象 # 导包 from pyspark import SparkConf, SparkContext# 创建SparkConf类对象 conf SparkConf()…

多行业预约门店服务小程序源码系统 支持多门店预约 带完整的安装代码包以及搭建教程

系统概述 该系统基于先进的云计算和大数据技术,采用模块化设计,具有高度的可扩展性和可定制性。无论是餐饮、美容美发、健身房还是其他服务行业,都可以通过该系统轻松实现多门店预约功能。同时,我们还提供了丰富的接口和插件&…

STM32-hal库学习(4)--usart/uart通信 (同时显示在oled)

前言: 关于usart详解: stm32-USART通信-CSDN博客 因为在oled上显示,我们直接在上一个工程进行修改: STM32_hal库学习(3)-OLED显示-CSDN博客 其他配置与oled显示工程保持不变,打开oled文件的…

html渲染的文字样式大小不统一解决方案

React Hooks 封装可粘贴图片的输入框组件(wangeditor)_react 支持图片拖拽的输入框-CSDN博客 这篇文章中的wangediter可粘贴图片的输入框,输入的文字和粘贴的文字在dangerouslySetInnerHTML渲染后出现了字体不统一的情况 在html中右键检查可…

短视频利器 ffmpeg (2)

ffmpeg 官网这样写到 Converting video and audio has never been so easy. 如何轻松简单的使用: 1、下载 官网:http://www.ffmpeg.org 安装参考文档: https://blog.csdn.net/qq_36765018/article/details/139067654 2、安装 # 启用RPM …

基于Java微信小程序火锅店点餐系统设计和实现(源码+LW+调试文档+讲解等)

💗博主介绍:✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者,博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌💗 🌟文末获取源码数据库🌟感兴趣的可以先收藏起来,还…

Dataease配置Nginx代理

Dataease配置Nginx代理 一.修改前端静态资源地址和后端接口地址 **1.**修改应用程序的上下文路径 配置文件地址:backend/src/main/resources 找到文件application-whole.properties,做如下修改: **2.**修改前端静态资源路径和打包配置 配…

【基于深度学习方法的激光雷达点云配准系列之GeoTransformer】——模型部分浅析(1)

【GeoTransformer系列】——模型部分 1. create_model2. model的本质3. 模型的主要结构3.1 backbone3.2 transformer本篇继续对GeoTransformer/experiments/geotransformer.kitti.stage5.gse.k3.max.oacl.stage2.sinkhorn/下面的trainval.py进行详细的解读,主要是模型部分, 可以…

【机器学习】ChatTTS:开源文本转语音(text-to-speech)大模型天花板

目录 一、引言 二、TTS(text-to-speech)模型原理 2.1 VITS 模型架构 2.2 VITS 模型训练 2.3 VITS 模型推理 三、ChatTTS 模型实战 3.1 ChatTTS 简介 3.2 ChatTTS 亮点 3.3 ChatTTS 数据集 3.4 ChatTTS 部署 3.4.1 创建conda环境 3.4.2 拉取源…

MySQL一条SQL语句的执行过程

例:SELECT * FROM USERS WHERE age 18 AND name student; 执行过程如下图: 综合上面的说明,我们分析下这个语句的执行流程: 1、使用连接器通过客户端/服务器通信协议与MySQL建立连接,并查询是否有权限。 2、MySQL8.…

[电子电路学]电路分析基本概念1

第一章 电路分析的基本概念和基本定律 电路模型 反映实际电路部件的主要电磁性质的理想电路元件及其组合,是实际电路电气特性的抽象和近似。 理想电路元件 实际电路器件品种繁多,其电磁特性多元而复杂,分析和计算时非常困难。而理想电路元件…

C# 在WPF .net8.0框架中使用FontAwesome 6和IconFont图标字体

文章目录 一、在WPF中使用FontAwesome 6图标字体1.1 下载FontAwesome1.2 在WPF中配置引用1.2.1 引用FontAwesome字体文件1.2.2 将字体文件已资源的形式生成 1.3 在项目中应用1.3.1 使用方式一:局部引用1.3.2 使用方式二:单个文件中全局引用1.3.3 使用方式…

Linux基础 - 使用 vsftpd 服务传输文件

零. 简介 文件传输协议(File Transfer Protocol,FTP)是用于在网络上进行文件传输的标准网络协议。 FTP 允许客户端和服务器之间进行文件的上传、下载、删除、重命名等操作。它基于客户端 - 服务器模型工作,通常使用 TCP 协议进行…

Arduino - Keypad 键盘

Arduino - Keypad Arduino - Keypad The keypad is widely used in many devices such as door lock, ATM, calculator… 键盘广泛应用于门锁、ATM、计算器等多种设备中。 In this tutorial, we will learn: 在本教程中,我们将学习: How to use key…

高性价比蓝牙耳机有哪些?2024超高性价比蓝牙耳机推荐

在2024移动互联网高速发展的时代,蓝牙耳机已成为我们生活中不可或缺的一部分。走在街头,低头看手机,滑动屏幕选歌,耳边传来清晰的旋律,这一幕已经成为现代生活的标配。但面对市场上琳琅满目的蓝牙耳机品牌和型号&#…

LabVIEW在光学与光子学实验室中的应用

光学与光子学实验室致力于光学和光子学前沿领域的研究,涉及超快光学、非线性光学、光纤通信、光子晶体等多个方向。实验室需要高精度的实验控制和数据采集系统,以进行复杂的光学实验,并对实验数据进行实时处理和分析。 项目需求 实时控制与监…