Kotlin 高级 :Inline Class
内联类
有时候,业务逻辑需要围绕某种类型创建包装器。然而,由于额外的堆内存分配问题,它会引入运行时的性能开销。此外,如果被包装的类型是原生类型,性能的损失是很糟糕的,因为原生类型通常在运行时就进行了大量优化,然而他们的包装器却没有得到任何特殊的处理。
为了解决这类问题,Kotlin 引入了一种被称为 内联类 的特殊类,它通过在类的前面定义一个 inline 修饰符来声明:
inline class Password(val value: String)
内联类必须拥有唯一的一个属性, 并在主构造器中初始化这个属性. 在运行期, 会使用这个唯一的属性来表达内联类的实例:
// 'Password' 类的实例不会真实存在
// 在运行期, 'securePassword' 只包含 'String'
val securePassword = Password("Don't try this in production")
这就是内联类的主要功能, 受 “内联” 这个名称的启发而来: 类中的数据被 “内联” 到使用它的地方 (类似于 内联函数 的内容被内联到调用它的地方).
内联类的成员
内联类支持与通常的类相同的功能. 具体来说, 内联类可以声明属性和函数:
inline class Name(val s: String) {
val length: Int
get() = s.length
fun greet() {
println("Hello, $s")
}
}
fun main() {
val name = Name("Kotlin")
name.greet() // 方法 `greet` 会作为静态方法来调用
println(name.length) // 属性的取值函数会作为静态方法来调用
}
但是, 对于内联类的成员存在一些限制:
- 内联类不能拥有 init 代码段
- 内联类的属性不能拥有 后端域变量back field因此, 内联类只能拥有简单的计算属性 (不能拥有延迟初始化属性或委托属性)
内联类的继承
1. 内联类只允许继承接口
interface Printable {
fun prettyPrint(): String
}
inline class Name(val s: String) : Printable {
override fun prettyPrint(): String = "Let's $s!"
}
fun main() {
val name = Name("Kotlin")
println(name.prettyPrint()) // 仍然是调用静态方法
}
2. 内联类不能继承其他类
3. 内联类是 final 类, 不能被其他类继承
内联类的内部表达
在通常的代码中, Kotlin 编译器会对每个内联类保留一个 包装. 内联类的实例在运行期可以表达为这个包装, 也可以表达为它的底层类型. 类似于 Int 可以 表达 为基本类型 int, 也可以表达为包装类 Integer。
Kotlin 编译器会优先使用底层类型而不是包装类, 这样可以产生最优化的代码, 运行时的性能也会最好. 但是, 有些时候会需要保留包装类. 一般来说, 当内联类被用作其他类型时, 它会被装箱(box)。
interface I
inline class Foo(val i: Int) : I
fun asInline(f: Foo) {}
fun <T> asGeneric(x: T) {}
fun asInterface(i: I) {}
fun asNullable(i: Foo?) {}
fun <T> id(x: T): T = x
fun main() {
val f = Foo(42)
asInline(f) // 拆箱: 用作 Foo 本身
asGeneric(f) // 被装箱: 被用作泛型类型 T
asInterface(f) // 被装箱: 被用作类型 I
asNullable(f) // 被装箱: 被用作 Foo?, 这个类型与 Foo 不同
// 下面的例子中, 'f' 首先被装箱(传递给 'id' 函数), 然后被拆箱 (从
'id' 函数返回)
// 最终, 'c' 中包含拆箱后的表达(也就是 '42'), 与 'f' 一样
val c = id(f)
}
由于内联类可以表达为底层类型和包装类两种方式, 引用相等性 对于内联类是毫无意义的, 因此禁止对内联类进行引用相等性判断操作。
内联类中的函数名称混淆
由于内联类被编译为它的底层类型, 因此可能会导致一些令人难以理解的错误, 比如, 意料不到的平台签名冲突:
inline class UInt(val x: Int)
// 在 JVM 平台上表达为 'public final void compute(int x)'
fun compute(x: Int) { }
// 在 JVM 平台上也表达为 'public final void compute(int x)'!
fun compute(x: UInt) { }
为了解决这种问题, 使用内联类的函数会被进行名称 混淆, 方法是对函数名添加一些稳定的哈希值. 因此, fun compute(x: UInt) 会表达为 public final void compute-(int x), 然后就解决了函数名称的冲突问题:
注意, 在 Java 中 - 是一个 无效的 符号, 也就是说从 Java 中无法调用那些使用了内联类作为参数的函数:
注意, 在 Java 中 - 是一个 无效的 符号, 也就是说从 Java 中无法调用那些使用了内联类作为参数的函数.
但是, 主要的差别在于, 类型别名与它的底层类型是 赋值兼容 的 (与同一个底层类型的另一个类型别名, 也是兼容的), 而内联类不是如此。
也就是说, 内联类会生成一个真正的 新 类型, 相反, 类型别名只是给既有的类型定义了一个新的名字(也就是别名):
typealias NameTypeAlias = String
inline class NameInlineClass(val s: String)
fun acceptString(s: String) {}
fun acceptNameTypeAlias(n: NameTypeAlias) {}
fun acceptNameInlineClass(p: NameInlineClass) {}
fun main() {
val nameAlias: NameTypeAlias = ""
val nameInlineClass: NameInlineClass = NameInlineClass("")
val string: String = ""
acceptString(nameAlias) // 正确: 需要底层类型的地方, 可以传入
类型别名
acceptString(nameInlineClass) // 错误: 需要底层类型的地方, 不
能传入内联类
// 反过来:
acceptNameTypeAlias(string) // 正确: 需要类型别名的地方, 可以
传入底层类型
acceptNameInlineClass(string) // 错误: 需要内联类的地方, 不能
传入底层类型
}
内联类功能还在实验性阶段
内联类的设计目前还处于实验性阶段, 也就是说这个功能正在 快速变化 中, 不保证兼容性. 在 Kotlin 1.3+ 中使用内联类时, 编译器会报告警告信息, 指出这个功能是实验性的。
要删除这些警告, 你需要对 kotlinc 指定 -XXLanguage:+InlineClasses选项, 来允许使用这个实验性功能。
在 Gradle 中启用内联类:
compileKotlin {
kotlinOptions.freeCompilerArgs += ["-
XXLanguage:+InlineClasses"]
}
More
1、Kotlin官方关于Inline Class 的说明
不需要打赏,麻烦点个赞。
网友评论