阅读完需:约 4 分钟
早在 kotlin 1.3 就已经有了 inline class 的alpha版本。到 1.4.30 进入 beta,如今在 1.5.0 中 终于迎来了 Stable 版本。早期的实验版本的 inline 关键字 在 1.5 中被废弃,转而变为 value关键字
//before 1.5
inline class Password(private val s: String)
//after 1.5 (For JVM backends)
@JvmInline
value class Password(private val s: String)
内联值类与之前内联函数是不同的,而1.5的改版正是为了区分内联函数与内联值类
- 容易跟内联函数混淆
- 让人误会编译器的置换机制
内联值类
inline class 主要就是用途就是更好地 “包装” value
有时为了语义更有辨识度,我们会使用自定义class包装一些基本型的value,这虽然提高了代码可读性,但额外的包装会带来潜在的性能损失,基本型的value由于被在包装在其他class中,无法享受到jvm的优化(由堆上分配变为栈上分配)。而 inline class 在最终生成的字节码中被替换成其 “包装”的 value, 进而提高运行时的性能。
早期:
// 函数
fun greetAfterTimeout(duration:Durations){
println(duration)
}
// class 类---实体类
class Durations(val millis: Long)
// 协助我们产生正确的时间长度
fun seconds(value:Int):Durations= Durations(value*1000L)
fun main() {
greetAfterTimeout(seconds(2)) // 类型不安全,不知道是 分,秒,毫秒 所以加入seconds函数来进行转换
}
内联值类:
fun greetAfterTimeout(duration:Durations){
println(duration)
}
// 底层 当一个函数传入内联值类时,编译器会把类换成基本类型
//fun greetAfterTimeout-R44Lxco(duration:Duration) ->> fun greetAfterTimeout-R44Lxco(duration:Long)
//class Duration(val millis: Long)
// 内联值类
@JvmInline
value class Durations(val millis: Long)
// 协助我们产生正确的时间长度
fun seconds(value:Int):Durations= Durations(value*1000L)
@OptIn(ExperimentalTime::class)
fun main() {
greetAfterTimeout(seconds(2)) // 类型不安全,不知道是 分,秒,毫秒 所以加入seconds函数
// 我们用 seconds 回传 Duration ,编译器也会把这个内联值类换掉
// greetAfterTimeout-R44Lxco(2*1000L)
}
底层——当一个函数传入内联值类时,编译器会把类换成基本类型
Duration 实例在字节码中被替换为 Long 类型
当我们查看两段程序的结果就能清楚的知道区别了:
早期的程序打印结果:
com.enmalvi.jvmiline.Durations@681a9515
内联值类打印结果:
Durations(millis=2000)
在我们使用 println(duration) 来打印输出,并没有用 println(duration.millis) 输出的时候,使用内联类的程序可以清楚的打印出结果,而另一个只能打印出地址。
使用内联值类的优势:
- 类型安全
- 基本类型
- 性能
- 可显式的表面特定类型
- 没有为了分配对象而损失性能
内联值类拓展
内联值类可以定义成员函数及属性,像这个例子它应该在每次访问的时候被计算出来
@JvmInline
value class Name(private val s:String){
val length:Int
get() = s.length
}
指定初始化化块
@JvmInline
value class Name2(val s:String){
init {
require(s.isNotEmpty())
}
}
构造参数中有且只能有一个成员变量,变量必须为 val
序列化
内联值类还可以序列化,但是与普通类的序列化结果会有所不同
普通类序列化
@Serialization
class Color(val rgb:Int){
}
@Serialization
data class NamedColor(val color: Color,val name: String)
fun main() {
println(JSONObject.toJSON(NamedColor(Color(0),"black")))
}
结果
{"color":{"rgb":0},"name":"black"}
内联值类
@Serialization
@JvmInline
value class Color(val rgb:Int){
}
@Serialization
data class NamedColor(val color: Color,val name: String)
fun main() {
println(JSONObject.toJSON(NamedColor(Color(0),"black")))
}
结果
{"name":"black","color-EsE2G5M":0}
从结果可以看出内联值类的序列化将直接输出值,并没有层次的包含。
注意事项
底层加上后缀是为了区分,不区分会在class字节码里造成一样的签名
@JvmInline
value class Names (val value:String)
@JvmInline
value class Password(val value: String)
fun record(name: Names){}
fun record(password: Password){}
//底层
//fun record-7dj_djg(name:String)
//fun record-Gwat88I(password:String)
- 防止重载函数的参数经过 inline 后出现相同签名的情况
- 防止从Java侧调用到参数经过 inline 后的方法
如果java想要调用有内联值类的方法需要加上@JvmName() 来指定名称,因为内联值类有后缀签名
// 从java调用 需要添加 @JvmName()
@JvmName("greetAfterTimeouts") // 其中 Durations 是内联值类
fun greetAfterTimeouts(duration:Durations){
println(duration)
}