Public get, private set 在 Kotlin 中:如何避免踩坑(以及踩坑后该怎么办)
\n\n你好,同行!如果你曾经搜索过 "Kotlin public get private set var",那么你并不孤单。这个查询是 Stack Overflow 上的真正热门,成千上万的开发者(包括曾经的我)都曾抓耳挠腮,试图理解为什么他们的代码不工作。
\n\n看起来有什么复杂的呢?我希望属性对所有代码可见(public get),但只有类本身可以修改它(private set)。在 Java 中,这是常规操作:声明一个 private 字段,编写 getter 和 setter,然后将 setter 设为 private。而在 Kotlin 中,一切本应更简单,但是...
\n\n剧透:语法是存在的,但它并不总是像你期望的那样工作。在本文中,我们将分析即使是经验丰富的开发者也会犯的顶级错误,并展示如何避免它们。开始吧!
\n\n属性的解剖:var, val, get 和 set
\n\n首先——快速科普。在 Kotlin 中,属性不仅仅是字段。它们是完整的元素,可以包含 getter 和 setter。以下是基本结构:
\n\nvar 属性名: 类型 = 默认值\n get() = field\n set(value) { field = value }\n\n如果你使用 var,Kotlin 会自动生成 getter 和 setter。但你可以覆盖它们的行为。此外,你还可以更改它们的可见性(可见性修饰符:public, private, internal, protected)。
而有趣的部分就在这里开始了。要使 getter 公开,setter 私有,你需要编写:
\n\nvar name: String = "default"\n private set\n\n就这些?差不多。但有一些细微差别会破坏这个机制。
\n\n错误 #1:忘记构造函数
\n\n最常见的问题——尝试在类的构造函数中声明 public get private set var。例如:
// ❌ 错误:不能这样写\nclass User(public get private set var name: String)\n\nKotlin 会抛出编译错误。为什么?因为构造函数的参数不是属性。它们只是参数。如果你希望属性具有修改后的 setter 可见性,则必须在类体中声明它。
\n\n正确做法:
\n\nclass User(name: String) {\n var name: String = name\n private set\n}\n\n或者,如果你使用 data class,则必须变通一下:
data class User(private val _name: String) {\n var name: String = _name\n private set\n}\n\n是的,data class 不允许直接更改主构造函数中属性的 setter 可见性。你必须使用幕后字段。
错误 #2:混淆幕后字段
\n\n幕后字段是 Kotlin 为属性自动生成的隐藏字段,前提是你在 getter 或 setter 中使用了 field。但是,如果你覆盖了 getter 或 setter 而没有引用 field,则不会创建幕后字段。
问题示例:
\n\nvar counter: Int = 0\n private set\n get() = field * 2 // ❌ 错误:field 未定义\n\n不,这不是编译错误。但如果你编写:
\n\nvar counter: Int = 0\n private set\n get() = 42 // 没问题,但未使用 field\n\n在这种情况下,不会创建幕后字段。如果你随后尝试在 setter 中引用 field,将会收到错误。好消息是:如果你使用 private set,幕后字段几乎总是会自动创建,因为默认的 setter 会使用 field。但如果你覆盖了 setter 而没有使用 field——准备好迎接惊喜吧。
建议:如果你想使用幕后字段,始终在 getter 和 setter 中显式使用 field。并且记住:如果属性没有幕后字段