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弱类型&异或取反&序列化&…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
GAN模式奔溃的探讨论文综述(一)
简介 简介:今天带来一篇关于GAN的,对于模式奔溃的一个探讨的一个问题,帮助大家更好的解决训练中遇到的一个难题。 论文题目:An in-depth review and analysis of mode collapse in GAN 期刊:Machine Learning 链接:...
