1. 概述
在本篇简明教程中,我们将了解 Kotlin 中的后端字段(Backing Fields)及其使用场景。
我们会先回顾 Kotlin 中属性和自定义访问器的基础知识,接着通过一个例子来展示后端字段的实际应用,最后我们会从字节码层面分析其底层实现。
2. 后端字段简介
Kotlin 支持声明可变(var
)和不可变(val
)属性。只需使用 val
或 var
关键字修饰属性即可:
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
可以看到,body
和 headers
是默认访问器,所以生成了后端字段;statusCode
因为使用了 field
,也生成了后端字段;而 hasBody
没有满足任何条件,因此没有对应的字段。
⚠️ 结论:只有在需要时 Kotlin 才会生成后端字段,避免不必要的内存开销。
4. 总结
我们回顾了 Kotlin 中属性和自定义访问器的基本用法,重点讲解了后端字段的概念、使用场景以及其在字节码中的表现形式。
- 后端字段用于存储属性值
- 在自定义 setter 中使用
field
可以避免递归调用 - Kotlin 只有在必要时才会生成后端字段,节省内存
所有示例代码已上传至 GitHub:点击查看。