Kotlin 中的内联函数

news2025/2/8 13:47:20

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 保留了内联函数的其他所有特性。

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

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

相关文章

【自撰写】【国际象棋入门】第8课 国际象棋残局基础

第8课 国际象棋残局基础 一、残局的特点 残局是棋局的最后(收尾)阶段,虽然此时棋盘上的子力已经所剩无几,但依照不同的局面分类,残局中存在着许多有意思的变化,初始局面中的细小变化也可能引发到截然不同…

Redis源码学习:ziplist的数据结构和连锁更新问题

ziplist ziplist 是 Redis 中一种紧凑型的列表结构&#xff0c;专门用来存储元素数量少且每个元素较小的数据。它是一个双端链表&#xff0c; 可以在任意一端进行压入/弹出操作&#xff0c;并且该操作的时间复杂度为O(1)。 ziplist数据结构 <zlbytes><zltail>&l…

期货交易豆粕品种详细分析

文章目录 1、豆粕期货标准&#xff08;2024年6月22号数据&#xff09;2、豆粕是什么3、豆粕1、5、9合约区别4、影响豆粕的价格因素1、大豆的供应情况。2、豆粕的季节性3、油粕比&#xff08;豆油和豆粕的价格关系 &#xff09; 5、美国大豆的生产/库存炒作6、豆粕双方&#xff…

Linux中tar压缩与解压缩

TAR是Unix/Linux中常用的归档工具&#xff0c;它可以对文件或目录进行打包但不压缩&#xff0c;或者配合其他工具进行压缩。 压缩文件或目录 以下是一些基本的tar压缩命令&#xff1a; 1.压缩单个文件&#xff1a; tar -cvf archive.tar file1 2.压缩多个文件&#xff1a; t…

微软Edge浏览器全解析

微软Edge浏览器全解析(一) 解决浏览器的主页被篡改后无法通过浏览器的自带设置来恢复的问题 相信各位都有发现新买的联想电脑浏览器的主页设置不太满意,但从浏览器自带的设置上又无法解决此问题,网上找了许多方法都无济于事,特别对有着强迫症的小伙伴们更是一种煎熬。 通…

cocos 如何使用九宫格图片,以及在微信小程序上失效。

1.在图片下方&#xff0c;点击edit。 2.拖动线条&#xff0c;使四角不被拉伸。 3.使用。 其他 在微信小程序上失效&#xff0c;需要将packable合图功能取消掉。

26.2 Django简介

1. Python三大主流框架 根据当前的信息和流行度, Python的三大框架可以归纳为Django, Flask和FastAPI, 以下是对它们的清晰分点表示和归纳:* 1. Django: 一个高级的Python Web框架, 以快速开发和实用简洁的设计出名.它遵循MVC设计模式(但更倾向于MTV模式), 并提供了许多内置的…

实施高效冷却技术:确保滚珠丝杆稳定运行!

滚珠丝杆在运行过程中&#xff0c;由于摩擦、惯性力等因素&#xff0c;会产生一定的热量&#xff0c;当热量无法及时散发时&#xff0c;滚珠丝杆的温度就会升高&#xff0c;会直接影响滚珠丝杆的精度和稳定性&#xff0c;从而影响最终的产品质量。为了让滚珠丝杆保持应有的精度…

【博客719】时序数据库基石:LSM Tree的增删查改

时序数据库基石&#xff1a;LSM Tree的增删查改 LSM结构 LSM树将任何的对数据操作都转化为对内存中的Memtable的一次插入。Memtable可以使用任意内存数据结构&#xff0c;如HashTable&#xff0c;BTree&#xff0c;SkipList等。对于有事务控制需要的存储系统&#xff0c;需要在…

数据库设计文档编写

PS&#xff1a;建议使用第三种方法 方法1&#xff1a;使用 Navicat 生成数据库设计文档 效果 先看简单的效果图&#xff0c;如果效果合适&#xff0c;大家在进行测试使用&#xff0c;不合适直接撤退&#xff0c;也不浪费时间。 随后在docx文档中生成目标字段的表格&#xf…

dex文件结构(二):dex文件加载基本原理

return mClassLoader; } 1.3 ApplicationLoaders.getClassLoader public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent){ //Class.getSystemClassLoader返回的是一个PathClassLoader //baseParent是BootClassLoader ClassLoader basePare…

【仿真建模-anylogic】Network代码解析

Author&#xff1a;赵志乾 Date&#xff1a;2024-06-22 Declaration&#xff1a;All Right Reserved&#xff01;&#xff01;&#xff01; 1. 类图 2. 代码解析 //************************核心字段************************* // Network所属的level private transient Leve…

windows10远程桌面端口,Windows 10远程桌面端口修改的两个方法

在Windows 10系统中&#xff0c;远程桌面功能允许用户通过网络从一台计算机远程访问和控制另一台计算机。默认情况下&#xff0c;远程桌面服务使用的端口是3389。然而&#xff0c;出于安全考虑&#xff0c;许多管理员和用户希望修改这一默认端口。本指南将详细介绍如何在Window…

乌班图Ubuntu 24.04 SSH Server 修改默认端口重启无效

试用最新的乌班图版本&#xff0c;常规修改ssh端口&#xff0c;修改完毕后重启sshd提示没有找到service&#xff0c;然后尝试去掉d重启ssh后查看状态&#xff0c;端口仍然是默认的22&#xff0c;各种尝试都试了不行&#xff0c;重启服务器后倒是端口修改成功了&#xff0c;心想…

AcWing算法基础课笔记——高斯消元

高斯消元 用来求解方程组 a 11 x 1 a 12 x 2 ⋯ a 1 n x n b 1 a 21 x 1 a 22 x 2 ⋯ a 2 n x n b 2 … a n 1 x 1 a n 2 x 2 ⋯ a n n x n b n a_{11} x_1 a_{12} x_2 \dots a_{1n} x_n b_1\\ a_{21} x_1 a_{22} x_2 \dots a_{2n} x_n b_2\\ \dots \\ a…

陈好与王星越中戏传承

陈好与王星越&#xff1a;中戏传承&#xff0c;万人迷与未来之星在娱乐圈的星光璀璨中&#xff0c;我们时常被那些耀眼的明星所吸引&#xff0c;但你是否曾想过&#xff0c;他们背后的成长之路&#xff0c;是如何被一位位优秀的老师所指引的呢&#xff1f;今天&#xff0c;就让…

刷代码随想录有感(113):动态规划——爬楼梯plus

题干&#xff1a; 代码&#xff1a; #include<bits/stdc.h> using namespace std;int main(){int n,m;cin>>n>>m;vector<int>dp(n 1, 0);dp[0] 1;for(int j 0; j < n; j){for(int i 1; i < m; i){if(j > i)dp[j] dp[j - i];}}cout<&…

交互式知识库问答:一种结合大型语言模型的多轮交互方法

在当今信息爆炸的时代&#xff0c;人们每天都要处理海量的数据和信息。在这样的背景下&#xff0c;基于知识库的问答系统&#xff08;KBQA&#xff09;成为了一个重要的研究领域&#xff0c;它旨在使计算机能够理解自然语言提出的问题&#xff0c;并从结构化的知识库中检索出准…

以太坊==给合约转入/查询合约剩余/合约转给某账户/结构体/MAP

转入 必须要定义该函数&#xff0c;或者定义fallback // 接收以太币 receive() external payable {} // Corrected Line // SPDX-License-Identifier: MIT pragma solidity ^0.8.0;contract SimpleStorage {uint256 private storedData;// 事件&#xff0c;用于通知数据变更e…

【Redis】黑马点评短信登录

https://blog.csdn.net/qq_33888850/article/details/129770077 https://blog.csdn.net/weixin_51515308/article/details/128010464 https://www.bilibili.com/video/BV1cr4y1671t?p24 导入数据库 https://github.com/MagicToDo/hm-dianping sql文件在 hm-dianping-init\src…