llms.txt: machine-readable documentation index. Markdown version of this page is also available.

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) { }

默认情况下, Kotlin 使用 未装箱的表达形式(unboxed representation) 来编译内联类, 因此在 Java 中难以访问. 关于如何将内联类编译为 装箱的表达形式(boxed representation), 使得 Java 中可以访问, 请参见向导 在 Java 中调用 Kotlin.

内联类与类型别名

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

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

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

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" }
2026/10/11