Public get private set 在 Kotlin 中的陷阱与解决方案

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

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\n
var 属性名: 类型 = 默认值\n    get() = field\n    set(value) { field = value }
\n\n

如果你使用 var,Kotlin 会自动生成 getter 和 setter。但你可以覆盖它们的行为。此外,你还可以更改它们的可见性(可见性修饰符:public, private, internal, protected)。

\n\n

而有趣的部分就在这里开始了。要使 getter 公开,setter 私有,你需要编写:

\n\n
var name: String = "default"\n    private set
\n\n

就这些?差不多。但有一些细微差别会破坏这个机制。

\n\n

错误 #1:忘记构造函数

\n\n

最常见的问题——尝试在类的构造函数中声明 public get private set var。例如:

\n\n
// ❌ 错误:不能这样写\nclass User(public get private set var name: String)
\n\n

Kotlin 会抛出编译错误。为什么?因为构造函数的参数不是属性。它们只是参数。如果你希望属性具有修改后的 setter 可见性,则必须在类体中声明它。

\n\n

正确做法:

\n\n
class User(name: String) {\n    var name: String = name\n        private set\n}
\n\n

或者,如果你使用 data class,则必须变通一下:

\n\n
data class User(private val _name: String) {\n    var name: String = _name\n        private set\n}
\n\n

是的,data class 不允许直接更改主构造函数中属性的 setter 可见性。你必须使用幕后字段。

\n\n

错误 #2:混淆幕后字段

\n\n

幕后字段是 Kotlin 为属性自动生成的隐藏字段,前提是你在 getter 或 setter 中使用了 field。但是,如果你覆盖了 getter 或 setter 而没有引用 field,则不会创建幕后字段。

\n\n

问题示例:

\n\n
var counter: Int = 0\n    private set\n    get() = field * 2 // ❌ 错误:field 未定义
\n\n

不,这不是编译错误。但如果你编写:

\n\n
var counter: Int = 0\n    private set\n    get() = 42 // 没问题,但未使用 field
\n\n

在这种情况下,不会创建幕后字段。如果你随后尝试在 setter 中引用 field,将会收到错误。好消息是:如果你使用 private set,幕后字段几乎总是会自动创建,因为默认的 setter 会使用 field。但如果你覆盖了 setter 而没有使用 field——准备好迎接惊喜吧。

\n\n

建议:如果你想使用幕后字段,始终在 getter 和 setter 中显式使用 field。并且记住:如果属性没有幕后字段

博客

书籍推荐