Kotlin inline、noinline、crossinline 深入解析
主要内容:
- inline
- 高价函数的原理分析
- Non-local returns
- noinline
- crossinline
inline
如果有C语言基础的,inline 修饰一个函数表示该函数是一个内联函数。编译时,编译器会将内联函数的函数体拷贝到调用的地方。我们先看下在一个普通的 kotlin 函数上使用 inline 关键字:
inline fun inlineFun() {println("from inlineFun")
}
会发现 IDE 会给出警告:

建议我们在高阶函数上使用 inline 关键字。
好,那我们来看下高阶函数。
高价函数的原理分析
下面是一个简单的高阶函数,函数参数是一个 function type 类型:
private fun proxy(action: () -> Unit) {println("start logging")action()println("end logging")
}
编译后对应的 Java 代码为:
private final void proxy(Function0 action) {String var2 = "start logging";System.out.println(var2);action();var2 = "end logging";System.out.println(var2);
}
会将 function type 编译成 Function0 类型,因为 action: () -> Unit括号内是无参的,所以是 Function0,如果是一个参数对应 Function1,以此类推。然后,我们调用上面的高阶函数 proxy:
fun invokeProxy() {proxy {println("eating")}
}
查看对应的字节码:
public final invokeProxy()VL0LINENUMBER 34 L0ALOAD 0GETSTATIC inline/InlineTest$invokeProxy$1.INSTANCE : Linline/InlineTest$invokeProxy$1;CHECKCAST kotlin/jvm/functions/Function0INVOKESPECIAL inline/InlineTest.proxy (Lkotlin/jvm/functions/Function0;)VL1LINENUMBER 38 L1RETURNL2LOCALVARIABLE this Linline/InlineTest; L0 L2 0MAXSTACK = 2MAXLOCALS = 1
可以看出,编译后会生成一个内部类:inline/InlineTest$invokeProxy$1,然后我们看下这个内部类长什么样:
final class inline/InlineTest$invokeProxy$1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 {// access flags 0x1041public synthetic bridge invoke()Ljava/lang/Object;L0LINENUMBER 15 L0ALOAD 0INVOKEVIRTUAL inline/InlineTest$invokeProxy$1.invoke ()VGETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;ARETURNMAXSTACK = 1MAXLOCALS = 1// access flags 0x11public final invoke()VL0LINENUMBER 36 L0LDC "eating"ASTORE 1L1GETSTATIC java/lang/System.out : Ljava/io/PrintStream;ALOAD 1INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)VL2L3LINENUMBER 37 L3RETURNL4LOCALVARIABLE this Linline/InlineTest$invokeProxy$1; L0 L4 0MAXSTACK = 2MAXLOCALS = 2// access flags 0x0<init>()VALOAD 0ICONST_0INVOKESPECIAL kotlin/jvm/internal/Lambda.<init> (I)VRETURNMAXSTACK = 2MAXLOCALS = 1// access flags 0x19public final static Linline/InlineTest$invokeProxy$1; INSTANCE// access flags 0x8static <clinit>()VNEW inline/InlineTest$invokeProxy$1DUPINVOKESPECIAL inline/InlineTest$invokeProxy$1.<init> ()VPUTSTATIC inline/InlineTest$invokeProxy$1.INSTANCE : Linline/InlineTest$invokeProxy$1;RETURNMAXSTACK = 2MAXLOCALS = 0@Lkotlin/Metadata;(mv={1, 8, 0}, k=3, d1={"\u0000\u0008\n\u0000\n\u0002\u0010\u0002\n\u0000\u0010\u0000\u001a\u00020\u0001H\n\u00a2\u0006\u0002\u0008\u0002"}, d2={"<anonymous>", "", "invoke"})OUTERCLASS inline/InlineTest invokeProxy ()V// access flags 0x18final static INNERCLASS inline/InlineTest$invokeProxy$1 null null// compiled from: InlineTest.kt
}
上面的字节码,类似下面的伪代码:
final class InlineTest$invokeProxy$1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 {public final static InlineTest$invokeProxy$1 INSTANCE;static {INSTANCE = new InlineTest$invokeProxy$1()}public void invoke(){System.out.println("eating")}
}
可以看出,调用高阶函数 proxy,会生成内部类(该内部类是一个单例)将lambda体里的代码,拷贝到 invoke 函数里面。
小结:简单来说,就是有多少个调用点(call site 调用高阶函数的地方)就会产生多少个内部类。
我们继续往下看,如果在 lambda 表达式体里访问外部的变量呢:
class InlineTest {var age = 18private fun proxy(action: () -> Unit) {println("start logging")action()println("end logging")}fun invokeProxy() {proxy {age = 11 // 访问外部的成员变量println("eating")}}
}
invokeProxy对应的字节码如下:
public final invokeProxy()VL0LINENUMBER 34 L0ALOAD 0NEW inline/InlineTest$invokeProxy$1DUPALOAD 0INVOKESPECIAL inline/InlineTest$invokeProxy$1.<init> (Linline/InlineTest;)VCHECKCAST kotlin/jvm/functions/Function0INVOKESPECIAL inline/InlineTest.proxy (Lkotlin/jvm/functions/Function0;)VL1LINENUMBER 38 L1RETURNL2LOCALVARIABLE this Linline/InlineTest; L0 L2 0MAXSTACK = 4MAXLOCALS = 1
对应的 Java 伪代码如下:
public final void invokeProxy(){InlineTest$invokeProxy$1 function0 = new InlineTest$invokeProxy$1()proxy(function0)
}
该内部类 InlineTest$invokeProxy$1 变成如下:
final class InlineTest$invokeProxy$1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 {public void invoke(){InlineTest$invokeProxy$1.this.setAge(11)System.out.println("eating")}
}
可以看出每调用一次高阶函数 proxy 会都会创建一个内部类对象。
小结:
- 每个调用高阶函数地方(调用点),编译时,编译器都会生成一个内部类
- 调用高阶函数时,如果传入的 lambda 表达式体没有使用外部变量,那么只会用到内部类常量对象;如果 lambda 表达式体使用了外部变量,那么每次调用该高阶函数都会创建一个内部类对象。
如果在一个频繁触发的地方调用高阶函数,如自定义 draw 方法里,刚好 lambda 体又实用到了外部成员变量,这样就会隐性地在 draw 方法里频繁创建对象。
这样时候 inline 关键字就派上用场了。
将上面的高阶函数 proxy 使用 inline 修饰:
private inline fun proxy(action: () -> Unit) {println("start logging")action()println("end logging")
}
调用该高阶函数:
fun invokeProxyInline() {proxy {println("eating")}
}
字节码对应的 Java 代码如下:
public final void invokeProxyInline() {int $i$f$proxyInline = false;String var3 = "start logging";System.out.println(var3);int var4 = false;String var5 = "eating";System.out.println(var5);var3 = "end logging";System.out.println(var3);
}
可以看出调用 proxy 函数的地方不会创建内部类,而是将高阶函数的函数体拷贝到调用点。
Non-local returns
什么是 Non-local returns?我们先来看下什么是 return,下面是 kotlin 对 return 的定义:
by default returns from the nearest enclosing function or anonymous function.
意思就是:从离 return 最近的封闭函数或匿名函数中返回。举个例子:
fun test(age:Int) {if (age < 0) {return}println(age)
}
其中 test就是 enclosing function. 也就是说 return 的作用就是从一个函数中返回(离它最近的函数)。
搞清楚 return 关键字之后,我们来看下在 lambda 中使用 reutrn:
// 定义一个普通的高阶函数
private fun normalFunction(action: () -> Unit){println("start......")action()println("end......")
}main(){// 调用高阶函数normalFunction {return // 使用 return, 编译器报错}
}
发现编译器报错了,为啥不能在 lambda 中使用 return 呢?
首先,上面代码中里离 return关键字最近的 enclosing function是 main函数,有人可能会问,离 return最近的不是 normalFunction么?normalFunction只是一个函数调用,它不是一个封闭的函数,封闭的是 lambda 表达式。
其次,return也无法控制 normalFunction函数的 return的。因为 return所处的代码块只是 normalFunction的 lambda 参数而已,return控制的是 lambda。正如 Kotlin 官网所说的:
A bare return is forbidden inside a lambda because a lambda cannot make the enclosing function return.
因为在 lambda 中使用return,无法实现 return 的定义,所以无法在 lambda 中使用 return,如果是内联函数,则可以在 lambda 中使用 return:
private inline fun normalFunction(action: () -> Unit){println("start......")action()println("end......")
}main(){// 调用高阶函数normalFunction {return}
}
上面的代码是合法的(因为内联 normalFunction,编译时会将代码体拷贝到main函数中).
Kotlin 中把这种 return 称之为 Non-local returns(located in a lambda, but exiting the enclosing function).
Non-local returns 名字很好理解:return 的 local 是 lambda,而此处的 return 返回的是 lambda 外面的 main 函数(non-local),所以称之为 non-local returns.
noinline
noinline 顾名思义就是不内联。那是什么时候使用 noinline 呢?我们在上面 proxy 函数基础上做一个小修改:
private inline fun proxy(action: () -> Unit, action2: () -> Unit) {println("start logging")action()println("end logging")// action2 作为参数传递给另一个高阶函数cleanResource(action2)
}private fun cleanResource(execution: () -> Unit) {execution()println("cleaning resource1")println("cleaning resource2")
}
为了 proxy 新增了另一个参数 action2,然后将 action2 传递给高阶函数 cleanResource. 但是 IDEA 会提示如下错误:

提示我们使用 noinline 修饰 action2 参数:
private inline fun proxy(action: () -> Unit, noinline action2: () -> Unit) {println("start logging")action()println("end logging")// action2 作为参数传递给另一个高阶函数cleanResource(action2)
}
为什么不加 noinline 就会报错呢?
其实很好理解,因为 proxy 是一个 inline 函数,那么调用 proxy 的地方,编译器都会将函数体拷贝过去,包括传入的 lambda 参数(如上面的 action,action2),例如:
fun invokeProxy() {proxy({println("eating...")}, {println("eating...2")})
}
action对应的代码块是:println("eating..."),action2 对应的代码块是:println("eating...2")
因为action2传递给了 cleanResource,要想将代码块当做参数传递给函数,那么代码块用什么来表示,目前只能使用Class类。然而 proxy又是 inline 的,所以需要对 action2 参数单独处理,将其不要 inline。所以需要使用 oninline关键字来修饰 action2参数。
小结:如果需要将 inline 高阶函数的 lambda 参数传递给另一个高阶函数或作为函数的返回值,均需要使用 noinline 关键字修饰该参数。
crossinline
介绍完了 inline 和 noinline,我们来看下 crossinline。将上面的 proxy 函数,稍作修改:
private fun wrap(action: () -> Unit) {action.invoke()
}private inline fun proxy(action: () -> Unit) {println("start logging")wrap {action()}println("end logging")
}
编译器会报错,给出如下提示:

编译器报错原因:action 可能包含 Non-local returns,Kotlin 中不允许在非内联的 lambda 中使用 return(原因已经在 Non-local returns 章节已介绍了),也就是说 action 代码块中可能存在 return 关键字,需要使用 crossinline 来修饰 action 参数:
private inline fun proxy(crossinline action: () -> Unit) {println("start logging")wrap {action()}println("end logging")
}// 调用 proxy
private fun invokeProxy() {proxy{println("invoke acrossinline")}
}
我们看下 invokeProxy字节码:
// 内部类
public final class InlineTest$invokeProxy$$inlined$proxy$1 extends Lambda implements Function0 {public InlineTest$invokeProxy$$inlined$proxy$1() {super(0);}// $FF: synthetic method// $FF: bridge methodpublic Object invoke() {this.invoke();return Unit.INSTANCE;}public final void invoke() {int var1 = false;String var2 = "invoke acrossinline";System.out.println(var2);}
}private final void invokeProxy() {int $i$f$proxy = false;String var3 = "start logging";System.out.println(var3);// 每次都 new 一个内部类对象access$wrap(this, (Function0)(new InlineTest$invokeProxy$$inlined$proxy$1()));var3 = "end logging";System.out.println(var3);
}
可以看出会生成一个内部类:
InlineTest$invokeProxy$$inlined$proxy$1
并且每次调用都会创建一个内部类对象。crossinline 阻止了 action 参数内联。
可以到看 crossinline 的核心作用是阻止内联,那我们将 crossinline 换成 noinline 是不是也可以呢?
private inline fun proxy(noinline action: () -> Unit) {println("start logging")wrap {action()}println("end logging")
}
编译器不会报错,代码运行也是OK,貌似可以使用 noinline 代替 crossinline,既然可以用 noinline,为啥还搞个新的 crossinline 关键字?上面的代码虽然可以使用 noinline 代替 crossinline,但是底层还差别的。我们看下使用 noinline 对应的字节码:
public final invokeProxy()VL0LINENUMBER 135 L0ALOAD 0ASTORE 1GETSTATIC inline/InlineTest$invokeProxy$1.INSTANCE : Linline/InlineTest$invokeProxy$1;CHECKCAST kotlin/jvm/functions/Function0// 省略其他代码L5LINENUMBER 184 L5ALOAD 1NEW inline/InlineTest$proxy$1DUPALOAD 2INVOKESPECIAL inline/InlineTest$proxy$1.<init> (Lkotlin/jvm/functions/Function0;)VCHECKCAST kotlin/jvm/functions/Function0INVOKESTATIC inline/InlineTest.access$wrap (Linline/InlineTest;Lkotlin/jvm/functions/Function0;)V// 省略其他...
可见,使用 noinline 的话,会创建两个内部类:
inline/InlineTest$invokeProxy$1inline/InlineTest$proxy$1
至此,inline、noinline 和 crossinline 就介绍完毕了。
相关文章:
Kotlin inline、noinline、crossinline 深入解析
主要内容: inline 高价函数的原理分析Non-local returns noinlinecrossinline inline 如果有C语言基础的,inline 修饰一个函数表示该函数是一个内联函数。编译时,编译器会将内联函数的函数体拷贝到调用的地方。我们先看下在一个普通的 kot…...
在 CentOS 7 / RHEL 7 上安装 Python 3.11
原文链接:https://computingforgeeks.com/install-python-3-on-centos-rhel-7/ Python 是一种高级解释性编程语言,已被用于各种应用程序开发,并在近年来获得了巨大的流行。Python 可用于编写广泛的应用程序,包括 Web 开发、数据分…...
SVN基本使用笔记——广州云科
简介 SVN是什么? 代码版本管理工具 它能记住你每次的修改 查看所有的修改记录 恢复到任何历史版本 恢复己经删除的文件 SVN跟Git比,有什么优势 使用简单,上手快 目录级权限控制,企业安全必备 子目录Checkout,减少不必要的文件检出…...
python爬虫-Selenium
一、Selenium简介 Selenium是一个用于Web应用程序测试的工具,Selenium 测试直接运行在浏览器中,就像真正的用户在操作一样。模拟浏览器功能,自动执行网页中的js代码,实现动态加载。 二、环境配置 1、查看本机电脑谷歌浏览器的版…...
flutter plugins插件【一】【FlutterJsonBeanFactory】
1、FlutterJsonBeanFactory 在Setting->Tools->FlutterJsonBeanFactory里边自定义实体类的后缀,默认是entity 复制json到粘贴板,右键自己要存放实体的目录,可以看到JsonToDartBeanAction Class Name是实体名字,会默认加上…...
系统中出现大量不可中断进程和僵尸进程(理论)
一 进程状态 当 iowait 升高时,进程很可能因为得不到硬件的响应,而长时间处于不可中断状态。从 ps 或者 top 命令的输出中,你可以发现它们都处于 D 状态,也就是不可中断状态(Uninterruptible Sleep)。 R …...
L1-012 计算指数(Python实现) 测试点全过
前言: {\color{Blue}前言:} 前言:本系列题使用的是“PTA中的团体程序设计天梯赛——练习集”的题库,难度有L1、L2、L3三个等级,分别对应团体程序设计天梯赛的三个难度,如有需要可以直接查看对应专栏。发布个…...
String、StringBuffer、StringBuilder的区别
String、StringBuffer、StringBuilder的区别 String的内容不可修改,StringBuffer与StringBuilder的内容可以修改.StringBuffer与StringBuilder(更快)大部分功能是相似的StringBuffer采用同步处理,属于线程安全操作;而S…...
.net基础概念
1. .NET Framework .NET Framework开发平台包含公共语言运行库(CLR)和基类库(BCL),前者负载管理代码的执行,后者提供了丰富的类库来构建应用程序。.NET Framework仅支持Windows平台 2. Mono 由于.NET Framework支支持windows环境,因此社区…...
电缆工厂 3D 可视化管控系统 | 智慧工厂
近年来,我国各类器材制造业已经开始向数字化生产转型,使得生产流程变得更加精准高效。通过应用智能设备、物联网和大数据分析等技术,企业可以更好地监控生产线上的运行和质量情况,及时发现和解决问题,从而提高生产效率…...
bazel高效使用和调优
Bazel 为了正确性和高性能,做了很多优秀的设计,那么我们如何正确的使用这些能力,让我们的构建性能“起飞”呢, 我们将从本地研发和 CI pipeline 两种场景进行分析。 本地研发 本地研发通常采用默认的 Bazel 配置即可,…...
【实训项目】传道学习助手APP设计
1.设计摘要 跨入21世纪以来,伴随着时代的飞速发展,国民对教育的重视度也有了进一步的提升。我们不难发现虽然很多学习内容有学习资料或者答案,但是这些内容并不能达到让所有求学的人对所需知识进行完全地理解与掌握。所以我们需要进行提问与求助。那么一…...
短信验证码服务
使用的是 阿里云 阿里云官网 1.找到 左上角侧边栏 -云通信 -短信服务 2.在快速学习测试处 ,按照步骤完成快速学习,绑定要测试的手机号,选专用 【测试模板】,自定义模板需要人工审核,要一个工作日 3.右上角 获取 Acces…...
windows如何更改/禁用系统更新
提示:首先说明这属于将更新时间更改,不过你可以的将更新时间更改为十年一百年 废话不多说开始正文: 1.首先:winR打开运行,输入regedit,进入注册表编辑器 2.进入编辑器后依次点击:HKEY_LOCAL_MACHINE\SOFT…...
Clion 使用ffmpeg 学习1 开发环境配置
Clion 使用ffmpeg 学习1 开发环境配置 一、准备工作1. 准备环境2. 下载FFmpeg 二、操作步骤1. Clion 新建一个C项目2. 修改 CMakeLists.txt3. 修改配置4. 运行测试5. 打印rtsp 流信息的 demo 一、准备工作 在视频处理和多媒体应用程序开发中,FFmpeg 是一个强大的开…...
浏览器连不上 Flink WebUI 8081 端口
安装 flink-1.17.0 后,start-cluster.sh 启动,发现浏览器连不上 Flink WebUI 的8081端口。 问题排查: command R,输入cmd,检查宿主机能否ping通虚拟机,发现能ping通。 检查是否有flink以外的任务占用8081…...
Doris集群安装部署(1.2.4.1 release)
此文阅读需要有Linux和服务器硬件基础!某些内容写的不是特别细,如果常见的linux基础命令tar、uzip、mv、mkdir、系统包的安装等等,以文字带过了,这样可以减少文章篇幅。官方的安装部署方式一定要好好看一下,最好是尝试…...
对HashMap的value做升序、降序
public class MapUtils {// Map的value值降序排序public static <K, V extends Comparable<? super V>> Map<K, V> sortDescend(Map<K, V> map) {List<Map.Entry<K, V>> list new ArrayList<>(map.entrySet());list.sort((o1, o2)…...
算法面试-深度学习基础面试题整理-AIGC相关(2023.9.01开始,持续更新...)
1、stable diffusion和GAN哪个好?为什么 ? Stable diffusion是一种基于随机微分方程的生成方法,它通过逐步增加噪声来扰动原始图像,直到完全随机化。然后,它通过逐步减少噪声来恢复图像,同时使用一个神经网…...
Python、PHP和Java下的反序列化漏洞复现实例
环境准备 这篇文章旨在用于网络安全学习,请勿进行任何非法行为,否则后果自负。 python反序列化 p83 CTF夺旗 Python考点SST&反序列化&字符串_正经人_____的博客-CSDN博客 php反序列化 p84 CTF夺旗-PHP弱类型&异或取反&序列化&…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
