extension property — 是 Kotlin 的一种机制,允许向现有类添加新属性而无需继承和修改源代码。与 extension functions 不同,扩展属性不能存储状态 — 它们只能用 getter 和可选的 setter 声明,因为它们没有 backing field。根据 Kotlin Documentation, 2025,extension properties 被编译为 getter 和 setter 的静态方法,以 receiver 作为第一个参数。
要点
extension property — 是 Kotlin 的一种语法结构,它向现有类型添加属性而不修改其声明。属性通过指定 receiver 类型声明,并且必须包含 getter。与普通属性的主要区别 — 缺少 backing field:extension property 不能存储数据,只能基于 receiver 对象计算它们。
根据 Kotlin Foundation Survey (2024),extension properties 不如 extension functions 那样受欢迎 — 大约 45% 的开发人员定期使用它们。这是因为缺少状态的限制,这限制了应用领域。然而,对于与类型逻辑相关的计算属性,extension properties 是最简洁的选择。
Extension properties 被编译为一对静态的 getter 和 setter 方法。在字节码层面上,调用 extension property 和 extension function 之间没有区别 — 两者都成为带有 receiver 参数的静态方法。据 JetBrains (Kotlin Docs, 2025),额外开销完全不存在。
使用 extension properties 处理短的计算值,它们应该看起来像属性,而不是方法调用 — 这提高了代码的可读性并遵守统一访问原则。
声明 extension property 使用与普通属性类似的语法,但带有 receiver 类型的前缀。val 声明一个只读的 extension property,带有必须的 getter;var 声明一个可变的,带有 getter 和可选的 setter。
// 只读 extension property
val String.isEmail: Boolean
get() = this.contains("@") && this.contains(".")
// 调用
val valid = "test@test.com".isEmail
注意:extension property 无括号调用 — str.isEmail,而不是 str.isEmail()。这是 extension property 和 extension function 之间的主要区别:property 看起来像字段,尽管实际上通过 getter 计算。
Extension properties 可以是泛型的 — receiver 可以使用泛型参数。这允许创建与任何类型集合工作的通用属性。
val List<T>.secondOrNull: T?
get() = if (size >= 2) this[1] else null
val items = listOf("a", "b", "c")
val second = items.secondOrNull // "b"
secondOrNull 属性对任何类型 T 都有效,返回列表的第二个元素,如果元素少于两个则返回 null。这是一个典型的示例,说明 extension property 比函数更合适 — 访问看起来像读取字段。
Extension property 不能有 backing field,因为它不添加到类的元数据中 — 它仅作为一对静态的 getter/setter 函数存在。Backing field(Kotlin 中的关键字 field)— 是类的内部字段,存储属性的值。Extension property 无法访问类的内部结构。
// ❌ 错误:extension property 不能有 backing field
var String.cachedValue: String
get() = "computed"
set(value) {
field = value // 字段不可访问!
}
// ✅ 正确:使用外部存储
val cache = MutableMap<String, String>()
var String.cachedValue: String
get() = cache[this] ?: ""
set(value) { cache[this] = value }
示例中的 外部 Map 解决了存储问题,但又引发了另一个 — 内存泄漏。通过 extension property 获取的值如果不清除,将永远存在于 Map 中。这个限制使得 extension properties 不适合缓存或存储临时数据。
对于缓存,建议使用 WeakHashMap 或带有自动清理机制的方法。JetBrains 建议避免在生产代码中使用带有外部存储的 var extension properties,除非仔细管理生命周期。
在 extension property 和 extension function 之间的选择取决于语义:属性描述对象的特征,函数描述动作。统一访问原则 (Uniform Access Principle) 表明:客户不应该知道值是计算还是存储的。如果值可以作为特征来表示(长度、大小、状态)— 使用 property。
| 标准 | Extension property | Extension function |
|---|---|---|
| 调用 | 无括号:obj.property | 有括号:obj.function() |
| 语义 | 特征、属性 | 动作、操作 |
| Backing field | 不支持 | 不适用 |
| 参数 | 仅 getter/setter | 任何参数 |
| 性能 | 相同(静态方法) | 相同(静态方法) |
| 示例 | text.length | text.isEmail() |
规则很简单:如果操作接受参数 — 使用 extension function。如果是没有参数的简单计算值 — extension property。据 Android Architecture Guide (Google, 2025),应优先使用 extension properties 访问数据,使用 extension functions 进行有副作用的操作。
使用关键字 var 的 extension property 支持 setter,但无法存储值 — setter 通常执行副作用或将数据保存到外部存储。语法 与类的可变属性类似。
// 带 setter 的可变 extension property
var StringBuilder.lastChar: Char
get() = this[length - 1]
set(value) {
this.setCharAt(length - 1, value)
}
val sb = StringBuilder("Kotlin")
println(sb.lastChar) // n
sb.lastChar = '!'
println(sb) // Kotli!
lastChar 属性 — 来自 Kotlin 文档的经典示例。Getter 返回 StringBuilder 的最后一个字符,setter 用新值替换它。注意:状态存储在 StringBuilder 本身中(通过 setCharAt),而不是在单独的字段中 — 这是 extension property 的正确使用。
在实际项目中,extension properties 最常用于简化集合数据的访问、计算 UI 元素的大小或状态以及在现有类之上创建方便的 API。Kotlin 标准库 积极使用这种机制:size、indices、lastIndex — 这些都是 extension properties。
// 集合的 extension properties
val List<Int>.sumFast: Int
get() = fold(0) { acc, i -> acc + i }
val String.half: String
get() = this.substring(0, length / 2)
// Android View 的 Extension property
val View.isVisible: Boolean
get() = visibility == View.VISIBLE
// 通过安全 receiver 进行 null 检查
val String?.isNullOrBlank: Boolean
get() = this == null || this.isBlank()
针对 View 的 isVisible extension property — 每个 Android 开发人员都应该知道的示例。可以用 view.isVisible 替代 view.visibility == View.VISIBLE。它不仅更简短,而且读起来像自然语言:“如果视图可见”。尽管简单,这样的属性显著提高了代码的可读性。
常见问题
不可以,extension properties 不能为 companion object 或 object declaration 声明。Extension 机制仅适用于类、接口和可空类型。对于 object,请使用普通的顶层函数。
Inline 属性(使用 inline 修饰符)— 是 Kotlin 的一种机制,用于调用 getter/setter 而不创建属性对象。Extension property 始终被编译为静态方法,而 inline 属性被编译为无包装的调用。它们解决不同的问题:extension property 向现有类型添加属性,inline 优化自己属性的调用。
支持,extension property 可以包含注解,但仅在声明层面。与普通类属性不同,无法单独注解 extension property 的 getter 或 setter。示例:@JvmName(“getIsValid”) val String.isValid get() = true。
不可以,extension properties 不能以 companion object 作为 receiver 声明。这是语言的限制 — extension property 仅与类型的实例一起工作,而 companion object 是静态上下文。使用顶层 extension functions 或常量。
微乎其微。每个 extension property 在编译后的字节码中添加一个静态 getter 方法(和可选的 setter)。相比之下,创建一个具有相同属性的封装类会添加整个类。Extension properties — 更轻量的功能扩展方法。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。