编程Kotlin 开发者

Kotlin 中关于类的关键字 'inline'(值类/行内类)是如何实现的?存在哪些限制,这些类在字节码级别是如何工作的,何时以及为什么应该使用它们?请给出示例并解释典型的复杂性。

用 Hintsage AI 助手通过面试

答案

在 Kotlin 中,inline class(自 Kotlin 1.5 开始称为“value class”)允许以最小的开销创建类型封装。在编译时,此类会在幕后被其内部值(value)替换,以避免创建对象的开销。

限制和特点:

  • 只能在主构造函数中包含一个属性。
  • 不允许存储引用等值(=== 不像平常那样工作)。
  • 值类不能是可继承类,也不能拥有除了其值以外的状态。
  • 并非所有泛型类型和平台 API 都能在没有装箱的情况下与行内/值类配合使用。
  • 值类不能包含 init 块,不能包含除值之外的字段,仅限于最小化函数。

示例:

@JvmInline value class UserId(val value: String) fun getUser(id: UserId) { println("Loading user with id: ${id.value}") } val id = UserId("XYZ") getUser(id) // 在幕后只是处理 String!

何时使用:

  • 为标识符、特殊值提供类型安全。
  • 在处理数百万个此类封装时提高性能(对象不被创建)。

陷阱问题

值类可以继承其他类或在接口/抽象类层次中使用吗?

答案: 不可以,值类不能继承其他类(不包括接口),不能对继承开放,不能包含 init 块和其他非静态字段。唯一可用的选项是实现接口。

示例:

interface Validatable { fun isValid(): Boolean } @JvmInline value class Email(val raw: String) : Validatable { override fun isValid() = raw.contains("@") }

由于不熟悉该主题而导致的实际错误示例


故事

Android 应用在将值类添加到 Parcelable 参数后,启动时间骤然增加:发现不正确的 @Parcelizevalue class 导致在每个序列化阶段进行装箱/拆箱,从而削弱了行内的优势。


故事

微服务开始积极使用值类用于 UserId 和 ProductId 以确保类型安全,但在许多地方,泛型函数要求反射,而反射无法与“封装”一起使用。单元测试意外失败,出现 ClassCastException。


故事

从 Java 迁移的代码开始用值类优化内部域类,但将其用作可空字段时导致意外的 null 指针异常,因为只有在外部值也为 null 时,值类才能为 null,这破坏了旧的不变性。