看完上篇大致能理解 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 的函数体直接贴进了 mainICONST_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函数的第二点我这里写一个简单的年龄判断如果小于18lambda表达式返回no反之yes这就奇怪了为什么会在return处报错呢。这就涉及到一个概念non-local returnkotlin官方文档对于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 (it18){return no } yes println(end) }这下是不是懂了加上inline后能return的原因在这里因为这里return可以找到age()了那上述的return “no”为什么报红因为它返回的是age()但age没有声明返回类型那就默认Unit把“no”删了就行了解释it18直接return由于age是:Unit不需要所有路径都 return不用担心返回条件不足。 然后lambda 里最后一行自动作为返回值既然return的是age(),那我想return字符串的话是不是给age加个:String就行了这里还是不建议age(): String 才会“条件不足”所以编译器提示加一个TODOit 18 lambda 返回 yesage() 继续走 末尾 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的浅薄理解看个乐呵兄弟们