☰
kotlin浅谈从高阶函数到inline noinline crossinline
2026/10/9 5:40:34 网站建设 项目流程

看完上篇,大致能理解 function type 与 lambda receiver 之间的关系。

下面来看一下一个高阶函数在编译之后会变成什么

查看字节码

Decompile

可以看到,高阶函数在编译之后,变成了普通的java函数

接下来看看,如果调用这个高阶函数,那它编译会是什么样子呢

直接给出编译后的解释

INVOKEDYNAMIC invoke()Lkotlin/jvm/functions/Function2; [ //运行时动态生成一个 Function2 对象 InlineKt.main$lambda$0(II)I // 这个对象背后调的静态方法(lambda 体)

运行时由 LambdaMetafactory 动态生成一个实现 Function2 的类

然后我进行第二次调用

看看第二次调用编译后的字节码会有什么不一样

区别在于

//第一次 main$lambda$0 //第二次 main$lambda$1

所以,每写一个 lambda,就多一个 INVOKEDYNAMIC,就多一个静态方法。 每次调用都会动态生成(或复用)一个 Function2 对象。

还有就是这个function2是 Kotlin 标准库里定义的一个接口,专门用来表示「接收 2 个参数、返回 1 个值」的函数

下面使用inline来定义高阶函数,然后调用它,看看会有什么不同

这里完全没有 INVOKEDYNAMIC,也没有 addDefined 调用

可以看到和普通高阶函数区别很大

INVOKEDYNAMIC 消失了 → 没有动态生成 Function2 对象

addDefined 调用消失了 → 因为 inline 把 addDefined 的函数体直接贴进了 main

ICONST_1 / ICONST_2 / ISTORE → 这是 addDefined 内部的代码,被内联展开后出现在 main 里

这就是 inline 的本质:把被调函数的代码“复制粘贴”到调用处,于是函数调用没了,lambda 对象也没了

比如随便写一个inline函数

看看反编译后是什么

很鲜明

public static final void main() { addDefined(1, 2, InlineKt::main$lambda$0); // ← 没 inline 的:还是调用 addDefined(1, 2, InlineKt::main$lambda$1); // ← 没 inline 的:还是调用 int $i$f$testInline = 1; // ← inline 函数的标记 int b$iv = 2; int $i$f$addDefinedInline = 0; int var5 = 0; int a = $i$f$testInline + b$iv; // ← 函数体直接出现在 main 里! System.out.println(a); $i$f$testInline = 0; System.out.println("测试"); // ← 函数体直接出现在 main 里! System.out.println("测试"); System.out.println("测试"); System.out.println("测试"); System.out.println("测试"); }

由此可以看出,inline对高阶函数有重要作用

如果我对一个普通函数使用inline,会有这样的提示

编译器也告诉我们对于普通函数 “内联(inline)带来的性能提升微乎其微。内联最适合用在「参数是函数类型」的函数上。”

对高阶函数用 inline ,就是把它带来的那两样开销——FunctionN 对象 + invoke 调用——彻底消掉

接下来来看关于inline函数的第二点

我这里写一个简单的年龄判断,如果小于18,lambda表达式返回no反之yes

这就奇怪了,为什么会在return处报错呢。

这就涉及到一个概念non-local return

kotlin官方文档对于return的解释

return默认从最近的包围函数(封闭函数)/匿名函数返回,就比如下面这个

没有报错,因为这个return的封闭函数就是上面的ageTest()

那回到上面的

那这个return上面的是不是这个ageJudge,显然不是,因为他只是一个函数的调用,不是封闭函数

那再往上找,是不是return上面这个age()呢,那肯定又做不到,因为它传进来是个lambda表达式,怎么能跳出去来return age()呢,与return的定义就不符。

文档里也有所提及

不过正如之前的文章里讲的,可以使用标签来return(其实编写的时候编译器就提醒了)

打一个这样的标签就可以用了

还有就是给函数加个inline就可以用return了

但为什么会报错呢????接下来我来形象化演示一下加完inline会是什么样,就会理解了

根据上述得到的inline相关知识,会将函数体直接拷贝到调用处,那么

fun age(){ println("begin") println(x(1)) //调用x,即把lambda拿过来 println("end") }

接下来就会变成

fun age(){ println("begin") it: Int-> if (it<18){return "no" } "yes" println("end") }

这下是不是懂了,加上inline后能return的原因在这里,因为这里return可以找到age()了,那上述的return “no”为什么报红?因为它返回的是age(),但age没有声明返回类型(那就默认Unit),把“no”删了就行了

解释:it<18,直接return,由于age()是:Unit(不需要所有路径都 return),不用担心返回条件不足。 然后lambda 里最后一行自动作为返回值

既然return的是age(),那我想return字符串的话,是不是给age()加个:String就行了

这里还是不建议,

age(): String 才会“条件不足”,所以编译器提示,加一个TODO

it >= 18 lambda 返回 "yes",age() 继续走 末尾 return TODO(...),这样是可以的

不过还是建议:Unit最为方便

补充:

ok,现在来了解一下noinline。我给上面的函数加上一个参数:

然后我再写一个高阶函数

在ageJudge里调用它,看起来传y参数其实没问题,那为什么会报错呢

那调用一下它,然后形象化转换一下看看会变成什么样

现在形象化把函数体拷贝过来,

println("begin") { println("1") //第一个function type }) println("end") { clean(println("2")) //第二个function type }

会发现clean()里面直接是拷贝过来的函数体内容,这样根本用不了,所以编译器在写到这的时候会提示加一个noinline

拷贝函数体对这个y不生效,那他就可以用这个clean()(函数了

下面还有个知识点,是crossinline。通过上面了解了inline的非局部返回,即是inline的特权,也是inline的麻烦。为什么呢

当我对lambda进行一个处理,把调用function type放进另一个lambda里

block 可能包含非局部返回(return),但它现在被包进了 other 这个新 lambda 里,编译器没法保证 return 还能正确工作。要么加 crossinline,要么别这么用

crossinline 的作用:给参数加一个“禁止非局部返回”的约束。

加了 crossinline 后,block 里就不能写 return 了,既要内联,又不允许非局部返回,这样才能安全地转手。

以上就是我对从高阶函数到inline的浅薄理解,看个乐呵兄弟们

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询