1. 概述

在本篇简明教程中,我们将了解 Kotlin 中的后端字段(Backing Fields)及其使用场景。

我们会先回顾 Kotlin 中属性和自定义访问器的基础知识,接着通过一个例子来展示后端字段的实际应用,最后我们会从字节码层面分析其底层实现。

2. 后端字段简介

Kotlin 支持声明可变(var)和不可变(val)属性。只需使用 valvar 关键字修饰属性即可:

data class HttpResponse(val body: String, var headers: Map<String, String>)

在上面的例子中,编译器会为 body 生成 getter,为 headers 生成 getter 和 setter。这些属性的值在底层是通过 Java 字段来存储的,这些字段在 Kotlin 中被称为后端字段(Backing Field)

有时我们可能需要自定义访问器逻辑。例如,我们想为 hasBody 属性添加一个自定义 getter:

data class HttpResponse(val body: String, var headers: Map<String, String>) {
    val hasBody: Boolean
        get() = body.isNotBlank()
}

这个 getter 只是基于 body 属性的值进行判断,不需要单独存储状态,因此 Kotlin 不会为它生成后端字段。

现在我们来看一个需要后端字段的场景:我们希望 statusCode 的值必须在 100 到 599 之间:

var statusCode: Int = 100
    set(value) {
        if (value in 100..599) statusCode = value
    }

上面的写法会导致无限递归,因为我们在 setter 中又调用了 statusCode = value,这会再次触发 setter。

解决办法是使用 Kotlin 提供的隐式后端字段 field

var statusCode: Int = 100
    set(value) {
        if (value in 100..599) field = value
    }

这样我们就避免了递归调用,因为 field 直接操作的是底层存储字段。

3. 字节码表示

Kotlin 会在以下两种情况之一为属性生成后端字段:

  • 使用默认访问器(即没有显式定义 getter/setter)
  • 在自定义访问器中使用了 field 关键字

我们可以使用 kotlinc 编译 Kotlin 文件,再通过 javap 查看生成的字节码:

$ kotlinc BackingField.kt

然后查看字节码:

$ javap -c -p com.baeldung.backingfield.HttpResponse

输出结果如下:

public final class com.baeldung.backingfield.HttpResponse {
  private int statusCode;
  private final java.lang.String body;
  private java.util.Map<java.lang.String, java.lang.String> headers;
  // truncated

可以看到,bodyheaders 是默认访问器,所以生成了后端字段;statusCode 因为使用了 field,也生成了后端字段;而 hasBody 没有满足任何条件,因此没有对应的字段。

⚠️ 结论:只有在需要时 Kotlin 才会生成后端字段,避免不必要的内存开销。

4. 总结

我们回顾了 Kotlin 中属性和自定义访问器的基本用法,重点讲解了后端字段的概念、使用场景以及其在字节码中的表现形式。

  • 后端字段用于存储属性值
  • 在自定义 setter 中使用 field 可以避免递归调用
  • Kotlin 只有在必要时才会生成后端字段,节省内存

所有示例代码已上传至 GitHub:点击查看


原始标题:Backing Fields in Kotlin