Kotlin-源码里成吨的-noinline-和-crossinline-是干嘛的?

——当然你得符合编译时常量的特征啊,不然会报错,不给编。

inline

让变量内联用的是 const;而除了变量,Kotlin 还增加了对函数进行内联的支持。在 Kotlin 里,你给一个函数加上 inline 关键字,这个函数就会被以内联的方式进行编译。但!虽然同为内联,inline 关键字的作用和目的跟 const 是完全不同的。

编译时常量为什么这么多限制?因为只有符合这些限制,编译器和 JVM 才有能力做优化,从而这种内联操作也才有意义。稍微复杂一点,就优化不动了。什么叫「稍微复杂」我不知道,但是函数内联这种操作,绝对算得上是相当复杂了,绝对优化不动的。其实真要较真起来,函数的内联确实会产生一种被动的优化,就是刚才我说的:去掉一个函数,调用栈少了一层,性能的损耗肯定会少一些,但实际上调用栈本身所造成的性能损耗本来就是非常小的,这个优化跟没优化差不多。这个事实可能不太符合我们的直觉,但你这样想一下:在我们看到的各种性能优化规范里,你有没有见过类似「少写几个方法来减少调用栈」这样的优化策略?没有吧?为什么?因为这种优化没有意义。而同时,函数内联不同于常量内联的地方在于,函数体通常比常量复杂多了,而函数内联会导致函数体被拷贝到每个调用处,如果函数体比较大而被调用处又比较多,就会导致编译出的字节码变大很多。我们都知道编译结果的压缩是应用优化的一大指标,而函数内联对于这项指标是明显不利的。所以靠 inline 来做性能优化?不存在的。

那么问题就来了:inline 是干嘛用的呢?

事实上,inline 关键字不止可以内联自己的内部代码,还可以内联自己内部的内部的代码。什么叫「内部的内部」?就是自己的函数类型的参数。

例如我把 hello() 函数的定义改成这样,给它增加一个函数类型的参数:

相应地,在调用处也需要填上这个参数。

我可以填成匿名函数的形式:

也可以简单点,写成 Lambda 表达式:

因为 Java 并没有对函数类型的变量的原生支持,Kotlin 需要想办法来让这种自己新引入的概念在 JVM 中落地。而它想的办法是什么呢?就是用一个 JVM 对象来作为函数类型的变量的实际载体,让这个对象去执行实际的代码。也就是说,在我对代码做了刚才那种修改之后,程序在每次调用 hello() 的时候都会创建一个对象来执行 Lambda 表达式里的代码,虽然这个对象是用一下之后马上就被抛弃,但它确实被创建了。

这有什么坏处?其实一般情况下也没什么坏处,多创建个对象算什么?但是你想一下,如果这种函数被放在循环里执行:

内存占用是不是一下就飚起来了?而且关键是,你作为函数的创建者,并不知道、也没法规定别人在什么地方调用这个函数,也就是说,这个函数是否出现在循环或者界面刷新之类的高频场景里,是完全不可控的。这样一来……这一类函数就全都有了性能隐患了。高阶函数是 Kotlin 相比起 Java 很方便的一个特性,但却有这么一个性能隐患,这……还让人怎么放心用啊?

这就是 inline 关键字出场的时候了。

刚才我说了,inline 关键字不止可以内联自己的内部代码,还可以内联自己内部的内部的代码,意思是什么呢,就是你的函数在被加了 inline 关键字之后,编译器在编译时不仅会把函数内联过来,而且会把它内部的函数类型的参数——那就是那些 Lambda 表达式——也内联过来。换句话说,这个函数被编译器贴过来的时候是完全展开铺平的:

经过这种优化,是不是就避免了函数类型的参数所造成的临时对象的创建了?这样的话,是不是就不怕在循环或者界面刷新这样的高频场景里调用它们了?

天然的性能缺陷,我们通过 inline 关键字让函数用内联的方式进行编译,来减少参数对象的创建,从而避免出现性能问题。

所以,inline 是用来优化的吗?是,但你不能无脑使用它,你需要确定它可以带来优化再去用它,否则可能会变成负优化。其实换个角度想想:既然 inline 是优化,为什么 Kotlin 没有直接开启它,而要把它做成选项,而且还是个默认关闭的选项?就是因为它还真不一定是优化,加不加它需要我们自己去做判断。那怎么去做这个判断呢?很简单,如果你写的是高阶函数,会有函数类型的参数,加上 inline 就对了。

嗯……不过如果你们团队对于包大小有非常极致的追求,也可以选择酌情使用 inline,比如对代码做严格要求,只有会被频繁调用的高阶函数才使用 inline——这个可能在实施上会有点难度,一般来说,按我刚才说的原则就已经够了。

另外,Kotlin 的官方源码里还有一个 inline 的另类用法:在函数里直接去调用 Java 的静态方法:

用偷天换日的方式来去掉了这些 Java 的静态方法的前缀,让调用更简单:

这个很有必要跟大家提一下:这种用法不是 inline 被创造的初衷,也不是 inline 的核心意义,这属于一种相对偏门的另类用法。——不过这么用没什么问题啊,因为它的函数体简洁,并不会造成字节码膨胀的问题。你如果有类似的场景,也可以这么用。

讲到这儿……应该知道内联函数怎么用了吧?那我们就……继续深入一下?

noinline

说完 inline,我们来说另一个关键字:noinline。noinline 的意思很直白:inline 是内联,而 noinline 就是不内联。不过它不是作用于函数的,而是作用于函数的参数:对于一个标记了 inline 的内联函数,你可以对它的任何一个或多个函数类型的参数添加 noinline 关键字:

添加了之后,这个参数就不会参与内联了:

好理解吧?好理解是好理解,(皱眉)可是这有什么用啊?为什么要关闭这种优化?

首先我们要知道,函数类型的参数,它本质上是个对象。我们可以把这个对象当做函数来调用,这也是最常见的用法:

但同时我们也可以把它当做对象来用。比如把它当做返回值:

但当我们把函数进行内联的时候,它内部的这些参数就不再是对象了,因为他们会被编译器拿到调用处去展开。也就是说,当你的函数被这样调用的时候:

代码会被这样编译:

哎?请问你找谁啊?

发现问题了吗?当一个函数被内联之后,它内部的那些函数类型的参数就不再是对象了,因为它们的壳被脱掉了。换句话说,对于编译之后的字节码来说,这个对象根本就不存在。一个不存在的对象,你怎么使用?

所以当你要把一个这样的参数当做对象使用的时候,Android Studio 会报错,告诉你这没法编译:

那……我如果真的需要用这个对象怎么办?加上 noinline:

加了 noinline 之后,这个参数就不会参与内联了:

那我们就也可以正常使用它了。

所以,noinline 的作用是什么?是用来局部地、指向性地关掉函数的内联优化的。既然是优化,为什么要关掉?因为这种优化会导致函数中的函数类型的参数无法被当做对象使用,也就是说,这种优化会对 Kotlin 的功能做出一定程度的收窄。而当你需要这个功能的时候,就要手动关闭优化了。这也是 inline 默认是关闭、需要手动开启的另一个原因:它会收窄 Kotlin 的功能。

那么,我们应该怎么判断什么时候用 noinline 呢?很简单,比 inline 还要简单:你不用判断,Android Studio 会告诉你的。当你在内联函数里对函数类型的参数使用了风骚操作,Android Studio 拒绝编译的时候,你再加上 noinline 就可以了。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

总结

可以看出,笔者的工作学习模式便是由以下 「六个要点」 组成:

❝ 多层次的工作/学习计划 + 番茄工作法 + 定额工作法 + 批处理 + 多任务并行 + 图层工作法❞

希望大家能将这些要点融入自己的工作学习当中,我相信一定会工作与学习地更富有成效。

下面是我学习用到的一些书籍学习导图,以及系统的学习资料。每一个知识点,都有对应的导图,学习的资料,视频,面试题目。

**如:我需要学习 **Flutter的知识。(大家可以参考我的学习方法)

点击这里了解更多即可领取!
  • Flutter 的思维导图(无论学习什么,有学习路线都会事半功倍)

  • Flutter进阶学习全套手册

  • Flutter进阶学习全套视频

Flutter进阶学习全套手册**

[外链图片转存中…(img-TRjSLnSU-1711056342635)]

  • Flutter进阶学习全套视频

[外链图片转存中…(img-TO34IWho-1711056342635)]

大概就上面这几个步骤,这样学习不仅高效,而且能系统的学习新的知识。

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值