Kotlin 中的内联函数

1 inline

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

举例来说:

fun main() {
    val num1 = 100
    val num2 = 80
    val 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 method
   public 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(); // 2
      return 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 method
   public 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") // 1
    val str = ""
    printString(str) { s ->
        println("lambda start") // 3
        if (s.isEmpty()) return@printString // 局部返回
        println(s)
        println("lambda end")
    }
    println("main end") // 5
}

// 普通函数
fun printString(str: String, block: (String) -> Unit) {
    println("printString begin") // 2
    block(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") // 1
    val str = ""
    printString(str) { s ->
        println("lambda start") // 3
        if (s.isEmpty()) return // 可以直接使用 return 关键字
        println(s)
        println("lambda end")
    }
    println("main end")
}

// 内联函数
inline fun printString(str: String, block: (String) -> Unit) {
    println("printString begin") // 2
    block(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 保留了内联函数的其他所有特性。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

APT 组织也在利用云存储进行攻击

研究人员发现,各类攻击者都在攻击行动中将恶意脚本、远控木马和诱饵文档等恶意文件上传到云服务器上,各种恶意文件组合起来完成恶意攻击。 某个攻击组织从发送钓鱼邮件到植入远控木马的过程如下所示: 攻击链 多个恶意文件串联起了整个攻击行…

跑路代码已上线,坐等删库中~

前言 或许大家会认为删库跑路都是运维或者DBA的事情,或许认为我没有线上数据库权限就不可能删库跑路。但是事实并非如此,建议大家仔细阅读此文章,赶紧排查下您的代码,很可能隐藏着这种删库程序。还是要呼吁大家,这个案…

三级医院智慧医院信息化规划方案(65页PPT)

方案介绍: 该方案通过信息化手段实现医院信息化全覆盖,优化诊疗流程、提高诊疗效率和准确性;同时实现医疗资源的合理配置和共享,提升医疗服务质量。通过优化患者就医流程、提供便捷的服务和宣传健康知识等方式提高患者满意度。通…

苏州大学气膜综合馆成为师生活动新中心—轻空间

苏州大学应用技术学院的气膜综合馆自建成以来,已成为校园内的热门活动场所。由轻空间(江苏)膜科技有限公司(以下简称“轻空间”)全力打造,这座现代化、环保的多功能运动场馆,不仅为师生提供了一…

代码随想录第35天|动态规划

理论基础 动态规划是由前一个状态推导出来的, 而贪心是局部直接选取最优 五部曲: 确定dp数组(dp table)以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 debug过程 : dp数组打印查看 509. 斐波那契数 参考 //动态规划的方法 …

Python基础教程——常用的36个经典案例!

Python 的简洁和强大使其成为许多开发者的首选语言。本文将介绍36个常用的Python经典代码案例。这些示例覆盖了基础语法、常见任务、以及一些高级功能。(文末附带精品学习资料) 1. 列表推导式 fizz_buzz_list [ "FizzBuzz" if i % 15 0 else "Fizz&qu…

陪玩系统源码,陪玩平台源码,陪玩app源码搭建

游戏陪玩app开发,软件搭建,程序制作、系统设计 目前,中国约有五到六亿游戏玩家,其中大约有两亿人选择付费游戏。这显示了绝大多数玩家都愿意为他们喜欢的游戏付费。随着游戏体验的不断改善,越来越多的玩家更倾向于找到…

基于Java的汽车租赁系统【附源码】

论文题目 设计(论文)综述(1000字) 当今社会,汽车租赁已成为一种受欢迎的出行方式。本文旨在探讨汽车租赁行业的发展趋势、市场规模及其对环境的影响。目前,汽车租赁行业正在经历着快速的发展。随着经济的发…

3D资产爆发,轻量化需求再度冲高,见证下一代3D崛起!

数字经济不断发展,3D资产和实体经济迎来深度融合的窗口期,3D资产应用外延催生大量新场景、新业态,一个3D资产构建的数字世界正出现在我们眼前。 数字经济不断发展,3D资产和实体经济迎来深度融合的窗口期,3D资产应用外…

【TB作品】MSP430,G2533单片机,红外发射,红外接收,红外通信,IR发射

文章目录 题目红外NEC协议介绍基本概述数据帧结构位表示数据传输示例重复码(Repeat Code)实现细节发送端接收端 典型应用结论 最终效果代码 题目 遥控器 硬件:msp430g2553、oled显示器、ds18b20温度传感器、红外发射器、按键 软件功能&#…

OpenAI用GPT-4o打造癌症筛查AI助手;手机就能检测中风,准确率达 82%!中国气象局发布AI气象大模型...

AI for Science 企业动态速览—— * 皇家墨尔本大学用 AI 检测患者中风,准确率达 82% * OpenAI 用 GPT-4o 模型打造癌症筛查 AI 助手 * 中国气象局发布 AI 气象大模型风清、风雷、风顺 * AI 药企英矽智能:小分子抑制剂已完成中国 IIa 期临床试验全部患者…

Socket——向FTP服务器发送消息并获得响应

1、简介 Socket(套接字)是网络编程中用于描述IP地址和端口的一个抽象概念,通过它可以实现不同主机间的通信。套接字可以分为几种不同的类型,每种类型对应不同的协议和传输模式。 1.1、基本概念 IP地址:用于标识网络…

厂区滴漏智能识别摄像机

当今,随着智能技术的迅猛发展,智能识别摄像机正逐步应用于各个行业,特别是在工业生产环境中,其作用愈发凸显。其中,厂区滴漏智能识别摄像机的应用成为了保障生产安全和环境保护的重要手段之一。厂区滴漏智能识别摄像机…

简述Java项目中VO,BO,PO,DO,DTO之类的文件概念、易混点

VO,BO,PO,DO,DTO 概念易混点一:VO和DTO- 让我们通过一个实例来阐释DTO和VO的概念及其应用差异:小结:VO专注于展示,而DTO则用于数据的传输和业务逻辑的处理。 二:BO和PO小…

记录 Bonobo Git 服务器 SMTP 设置

Bonobo 使用标准的 .NET SMTP 设置&#xff0c;可以在 web.config 中指定这些设置。 <system.net><mailSettings><smtp deliveryMethod"network" from"bonobobonoserver.your.domain"><network host"accessible.smtp.host"…

用一个暑假|用AlGC-stable diffusion 辅助服装设计及展示,让你在同龄人中脱颖而出!

大家好&#xff0c;我是设计师阿威 Stable Diffusion是一款开源AI绘画工具&#xff0c; 用户输入语言指令&#xff0c;即可自动生成各种风格的绘画图片 Stable Diffusion功能强大&#xff0c;生态完整、使用方便。支持大部分视觉模型上传&#xff0c;且可自己定制模型&#x…

AI X HI:塑造数智时代的人类镜像,网易这场分享不能错过!

2001 年&#xff0c;网易正式成立在线游戏事业部。从那以后&#xff0c;网易孵化了许多出圈的精品游戏&#xff0c;跻身成为全球七大游戏公司之一。这些游戏产品之所以能够广受玩家好评&#xff0c;并保持常青&#xff0c;一方面源于十年磨一剑的精良品质&#xff0c;另一方面则…

基于微信小程序的在线点餐系统【前后台+附源码+LW】

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 点餐小程序&#xff0c;主要的模块包括实现管理员&#xff1b;管理员用户&#xff0c;可以对整个系统进行基本的增删改查&#xff0c;系统的日…

一文详解:生产计划和排产管理怎么做?

通过阅读本文&#xff0c;你可以了解以下内容&#xff1a;1、生产计划的制定&#xff1b;2、排产的策略和方法&#xff1b;3、生产计划和排产管理实施&#xff1b;4、生产计划和排产管理的效果评估。 一、生产计划制定 生产计划的本质就是协调企业一切资源“低成本、高质量”…

“RLC串联正弦稳态电路的仿真研究”课程设计,高分资源,匠心制作,下载可用。强烈推荐!!!

1.设计目的 用 Multisim 电路仿真软件&#xff0c;对一个 RLC 串联电路进行正弦稳态电路分析。 2任务分析 2.1任务要求1 在 Multisim 中搭建一个 RLC 串联电路&#xff0c;其中 R、 L、 C、正弦激励源的振幅Vp和频率 f 等所有参数均可自己任意设置&#xff08;不建议都采用…