kotlin value关键字以及inline class初探
inline Modifier
介绍
inline class 最早在 kotlin 1.2.30 中出现, 在 1.4.30 中到达 beta 版, 在 1.5 中可能变为 stable, 本文代码基于 Kotlin-1.5.0-M1.
可能很多 Javaer 并不会去在意 kotlin 那么多繁杂的关键字, 但我更喜欢追求高效率, 高易用性, 简洁舒适的代码, 好了, 我们来看看我们为什么要使用 inline class 呢?
我们知道, 在 JVM 中, 内存的储存是下面这样的:
当我们创建一个基本类型的局部变量的时候, int / float / boolean
等类型将会被储存在 jvm 的内存栈中, 这些基础类型储存在栈上, 访问/创建以及储存他们的开销并不会很大.
而当我们实例化一个对象的时候, 该对象实例就会储存在 JVM 堆上, 而堆内存的使用代价很高(详见下方 benchmark 部分), 虽然每个对象可能给我们的感觉不算很大, 但是大量的对象堆积起来对性能的影响不容轻视, 我们看下面一个例子:
我们定义了一个方法, 它需要传入一个时间参数:
|
|
这时候就出现了一个问题, 这个 time
究竟是小时? 分钟? 秒? 我们仅仅知道这是一个 Int
, 为了让编译器可以强制指定, 我们可以使用强类型语言的优势, 将函数这样封装, 我们就只能传入 Minute
, 因为它不支持传入其他类型
|
|
但是我们会发现在大多数开源库的源码中都不会选择这样的做法, 因为这样假如我们时间的类型是 Hour
, 我们还需要自己去 Hour
的类里面定义一个 toMinute
的方法, 但这样就会创建了一个我们并不会用到的对象 Hour
. 因此大多数开发者往往会定义一个 TimeUnit 作为额外的参数传入, 这样的做法我们已经习惯了, 但是我们能否做出改变呢?
在 kotlin 这里, 答案是肯定的: Inline Classes
|
|
那么这个关键字做了什么呢? 我们可以打开 kotlin 的字节码并将其反编译为 java, 我们就能发现其内部操作的实质:
|
|
我们来对比一下不使用 inline
关键字的时候字节码, 就知道发生了什么事情:
|
|
我们发现, 不使用 inline
时, 额外创建了一个 Hour
对象, 这也就是性能开销的原因了, 我们示例这个对象还比较简单, 试想如果在 Android 开发中, 你创建了一个 View
对象, 可想而知性能开销有多么恐怖, 当然, 在少量的情况下你不会感觉到性能区别有多大.
那么我们来解释一些东西, 我们首先实例化一个类:
|
|
事实上, 在使用了 inline
的情况下, 就 JVM 而言, 其内部会变成类似于这样的代码:
|
|
而我们上面实例中的 fk
函数:
|
|
也会变成类似于如下的代码
|
|
那么这时候就会出现一个问题, Hour
中的 toMinute()
方法怎么办?? 我们继续反编译其字节码, 我们会发现:
|
|
我们会发现! 它直接返回了一个 Minute
对象, 如果 Minute
我们也将其变成 inline
, 那么字节码会变成下面这样:
|
|
当然, 这样的 inline
到最后一层使用的时候还是会变成一个对象的(并不一定), 但是我们确实做到了减少了一个对象的创建!
不过其实如果我们最后需要使用的是原始类型的话, 我们倒也可以自己定义一个函数实现这种转换:
|
|
对于 JVM
, 就类似于这样:
|
|
事实上反编译字节码我们发现是这样的:
|
|
通过字节码, 我们还可以了解到这个 constructor-impl
实际上就是内部参数本身:
|
|
因此我们发现, 我们在并没有创建任何对象的情况下调用了两个对象的方法, 这样好!
限制
那么既然这么好, 那大家都用 inline
吧! 但是事实上, inline
有很多的限制
|
|
我们发现, 主构造器只能接受一个基础值作为成员属性, 但是它的内部是可以拥有成员属性
的, 只要它们仅基于构造器中那个基础值计算, 或者从可以静态解析的某个值或对象计算(单例,顶级对象,常量等)
|
|
还有更多的限制: 不允许继承, 但可以实现接口, 必须声明为顶层函数, 嵌套/内部类无法内连, 不支持枚举内联类, 具体可以看文章末尾的参考链接
Type Alias 不香吗
Type aliases 提供了我们另一种访问一个类的方式, 就像下面这样, 我们可以给 String
用其他的名字, 但事实上这样编译后还是原来的类型, 只是换了一种名字罢了, 我们传入不是这个名字的同一类型的照样可以实现访问
|
|
就像下面这样, 如果我们在username
传入了一个Password
, 编译依旧会照常通过, :
|
|
但如果我们换成了 inline
, 则会直接在我们写代码的时候爆红, 虽然他们本质上都会变成 String
, 但是这样提高了我们的可扩展性以及安全性!
|
|
value Modifier
介绍
这个关键词从 kotlin 1.4.30
引入, 我发现它是因为我偶然写代码的时候发现敲完 va 之后除了 val var
两个修饰符以外还有一个 value
修饰符! 于是赶紧去翻了官方文档看这玩意怎么用, 终于在 kotlin 1.4.30 的更新日志中发现了这个新关键字, 同时注意到官方说这个修饰符还处于 beta
阶段, 于是我下载了 kotlin 1.5-M1
去提前体验一些新特性
用法
|
|
这个@JvmInline
其实只是限定了 value
修饰符只能用在 JVM
平台, 具体有什么功能呢?
其实只是多了一个可以在 inline class 中写 init
罢了,需要注意的是 value
修饰符注解的类即是一个增强版的 inline
, 您无需再写 inline
修饰符:
|
|
这里插一句, 既然这玩意在 JVM
平台, 那么它理应能与 Java
进行互操作, 事实上也是这样的! 我们只需要使用 @JvmName
注解即可, 这个注解的主要用途就是告诉编译器生成的 Java 类或者方法的名称, 当然这只是可以这么干, 我觉得新写的代码你还用 Java 去调用 kotlin 的代码, 属实不优雅, 纯 kotlin 才是正道hhhhh
Benchmark
class 与 inline class 的性能相差了较为明显的数量级, 因为前者创建了6亿个对象, 而后者采用了内联的方式减少了很多对象的创建
class | inline class |
---|---|
ref:
- KEEP/inline-classes.md at master · Kotlin/KEEP (github.com)
- 如果上面链接不可用: cdn/value-classes.md at master · zsqw123/cdn (github.com)
- An Introduction to Inline Classes in Kotlin - Dave Leeds on Kotlin (typealias.com)
code: learn-kt/210323.kt at master · zsqw123/learn-kt (github.com)