Kotlin 语言参考文档 中文版 Help

内联的值类(Inline value class)

如果将值封装到类中, 创建一些特定领域的类型, 有时候会非常有用. 但是, 这就会产生堆上的内存分配, 带来运行时的性能损失. 更坏的情况下, 如果被包装的类是基本类型, 那么性能损失会非常严重, 因为在运行时对基本类型本来可以进行极大地性能优化, 而它的包装类却不能享受这种好处.

为了解决这类问题, Kotlin 引入了一种特别的类, 称为 内联类(inline class), 内联类是 基于值的类(value-based class)的一个子集. 这种类没有标识符, 只用于包含值.

声明内联类时, 在类名称之前添加 value 修饰符:

value class Password(private val s: String)

要在 JVM 后端上声明内联类, 需要在类的定义之前使用 value 修饰符和 @JvmInline 注解:

// 针对 JVM 后端 @JvmInline value class Password(private val s: String)

内联类必须拥有唯一的一个属性, 并在主构造器中初始化这个属性. 在运行期, 会使用这个唯一的属性来表达内联类的实例(关于运行期的内部表达, 请参见 下文):

// 'Password' 类的实例不会真实存在 // 在运行期, 'securePassword' 只包含 'String' val securePassword = Password("Don't try this in production")

这就是内联类的主要功能, 受 内联 这个名称的启发而来: 类中的数据被 内联 到使用它的地方 (类似于 内联函数 的内容被内联到调用它的地方).

成员

内联类支持与通常的类相同的功能. 具体来说, 内联类可以声明属性和函数, 也可以有 init 代码段和 次级构造器(secondary constructor):

@JvmInline value class Person(private val fullName: String) { init { require(fullName.isNotEmpty()) { "Full name shouldn't be empty" } } constructor(firstName: String, lastName: String) : this("$firstName $lastName") { require(lastName.isNotBlank()) { "Last name shouldn't be empty" } } val length: Int get() = fullName.length fun greet() { println("Hello, $fullName") } } fun main() { val name1 = Person("Kotlin", "Mascot") val name2 = Person("Kodee") name1.greet() // `greet()` 函数会作为静态方法来调用 println(name2.length) // 属性的取值函数会作为静态方法来调用 }

内联类的属性不能拥有 后端域变量. 只能拥有简单的计算属性 (不能拥有 lateinit 属性或委托属性)

继承

内联类允许继承接口:

interface Printable { fun prettyPrint(): String } @JvmInline value class Name(val s: String) : Printable { override fun prettyPrint(): String = "Let's $s!" } fun main() { val name = Name("Kotlin") println(name.prettyPrint()) // 仍然是调用静态方法 }

禁止内联类参与类继承. 也就是说, 内联类不能继承其他类, 而且它永远是 final 类, 不能被其他类继承.

内部表达

在通常的代码中, Kotlin 编译器会对每个内联类保留一个 包装. 内联类的实例在运行期可以表达为这个包装, 也可以表达为它的底层类型. 类似于 Int 可以 表达 为基本类型 int, 也可以表达为包装类 Integer.

Kotlin 编译器会优先使用底层类型而不是包装类, 这样可以产生最优化的代码, 运行时的性能也会最好. 但是, 有些时候会需要保留包装类. 一般来说, 当内联类被用作其他类型时, 它会被装箱(box).

interface I @JvmInline value 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) }

由于内联类可以表达为底层类型和包装类两种方式, 引用相等性 对于内联类是毫无意义的, 因此禁止对内联类进行引用相等性判断操作.

内联类也可以使用泛型类型参数作为底层类型. 这种情况下, 编译器将它映射为 Any?, 或者更一般的说, 映射为类型参数的上界(Upper Bound).

@JvmInline value class UserId<T>(val value: T) fun compute(s: UserId<String>) {} // 编译器生成的代码是 fun compute-<hashcode>(s: Any?)

函数名称混淆

由于内联类被编译为它的底层类型, 因此可能会导致一些令人难以理解的错误, 比如, 意料不到的平台签名冲突:

@JvmInline value 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-<hashcode>(int x), 然后就解决了函数名称的冲突问题.

在 Java 代码中调用

你可以在 Java 代码中调用接受内联类为参数的函数. 为了实现这一点, 你需要手动禁止函数名称混淆: 在函数声明之前添加 @JvmName 注解:

@JvmInline value class UInt(val x: Int) fun compute(x: Int) { } @JvmName("computeUInt") fun compute(x: UInt) { }

内联类与类型别名

初看起来, 内联类似乎非常象 类型别名. 确实, 它们都声明了一个新的类型, 并且在运行期都表达为各自的底层类型.

但是, 主要的差别在于, 类型别名与它的底层类型是 赋值兼容 的 (与同一个底层类型的另一个类型别名, 也是兼容的), 而内联类不是如此.

也就是说, 内联类会生成一个真正的 类型, 相反, 类型别名只是给既有的类型定义了一个新的名字(也就是别名):

typealias NameTypeAlias = String @JvmInline value 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) // 错误: 需要内联类的地方, 不能传入底层类型 }

内联类与代理

对于接口, 允许将它的实现代理给内联类的内联值:

interface MyInterface { fun bar() fun foo() = "foo" } @JvmInline value class MyInterfaceWrapper(val myInterface: MyInterface) : MyInterface by myInterface fun main() { val my = MyInterfaceWrapper(object : MyInterface { override fun bar() { // 函数体 } }) println(my.foo()) // 输出为 "foo" }
最终更新: 2024/10/17