编程Kotlin 开发者 / 性能工程师

Kotlin 中的内联属性访问器(内联 getter 和 setter)是如何工作的?它们的使用有什么特点和细微之处,它们如何影响性能,以及在哪里可能会出现意外错误?请给出一个示例。

用 Hintsage AI 助手通过面试

答案

Kotlin 允许用 inline 修饰符标记属性的 getter 和 setter。这使得编译器可以将访问器的代码直接内联到调用处,以优化性能。

示例:

val foo: Int inline get() = expensiveCalculation()
  • 由于内联,如果访问器的代码较短,getter 函数在调用时不会增加开销(例如,在循环中)。
  • 内联仅适用于无状态(stateless)getter;并非所有逻辑都允许内联。
  • 如果 set/get 方法包含了 reified generic 参数或包含交叉链接的 lambda 表达式,则不能使用 inline

最佳实践:仅对非常短且频繁调用、无副作用的表达式使用内联访问器。


陷阱问题

如果为带有内联 getter/setter 的属性添加了带反射的注解(例如,通过 KProperty 使用),内联会工作吗?

答案: 不会。如果属性通过反射 API 使用或引用 KProperty,编译器将无法内联 getter/setter,它们将保留为普通方法。内联仅在代码中的直接调用时发生。


历史

由于反射与内联 getter 的结合而导致性能损失:

将热点属性重写为内联 getter,希望消除多余的调用。后来通过 KProperty 添加了验证,最终调用通过反射发生,完全抵消了内联访问器的优势。


历史

内联时的意外副作用:

内联 getter 进行了日志记录:

inline get() { println("accessed!") return field }

这种实现方式在代码的多个地方读取属性时,会导致意外的日志记录,严重污染了日志。


历史

由于内联属性的变化导致 ABI 破裂:

更改了库中内联 getter 的逻辑,但没有重新构建依赖模块,客户端继续使用旧的签名——扩展的 ABI 加上内联导致了不兼容,并在更新时导致客户的隐秘崩溃。