Kotlin 中的 Extension property — 什么是、语法和应用

作者: IT Sectr 发布日期: 2026-06-21 阅读时间: 8 分钟

extension property — 是 Kotlin 的一种机制,允许向现有类添加新属性而无需继承和修改源代码。与 extension functions 不同,扩展属性不能存储状态 — 它们只能用 getter 和可选的 setter 声明,因为它们没有 backing field。根据 Kotlin Documentation, 2025,extension properties 被编译为 getter 和 setter 的静态方法,以 receiver 作为第一个参数。

要点

  • Extension property — 带有 receiver type 的属性,通过 Kotlin 属性语法可访问
  • 没有 backing field — extension property 不能存储状态,只能计算
  • Getter 是必须的,setter 是可选的 — 像普通的 extension functions 一样声明
  • 支持可空类型:receiver 可以是可空的,内部进行检查
  • 可变的 extension property — 仅当声明 var 并有 getter 和 setter 时

Kotlin 中的 extension property 是什么?

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 的语法:val 和 var

声明 extension property 使用与普通属性类似的语法,但带有 receiver 类型的前缀。val 声明一个只读的 extension property,带有必须的 getter;var 声明一个可变的,带有 getter 和可选的 setter。

kotlin
// 只读 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 property

Extension properties 可以是泛型的 — receiver 可以使用泛型参数。这允许创建与任何类型集合工作的通用属性。

kotlin
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 不能存储状态

Extension property 不能有 backing field,因为它不添加到类的元数据中 — 它仅作为一对静态的 getter/setter 函数存在。Backing field(Kotlin 中的关键字 field)— 是类的内部字段,存储属性的值。Extension property 无法访问类的内部结构。

kotlin
// ❌ 错误: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:什么时候选择什么

在 extension property 和 extension function 之间的选择取决于语义:属性描述对象的特征,函数描述动作。统一访问原则 (Uniform Access Principle) 表明:客户不应该知道值是计算还是存储的。如果值可以作为特征来表示(长度、大小、状态)— 使用 property。

标准Extension propertyExtension function
调用无括号:obj.property有括号:obj.function()
语义特征、属性动作、操作
Backing field不支持不适用
参数仅 getter/setter任何参数
性能相同(静态方法)相同(静态方法)
示例text.lengthtext.isEmail()

规则很简单:如果操作接受参数 — 使用 extension function。如果是没有参数的简单计算值 — extension property。 Android Architecture Guide (Google, 2025),应优先使用 extension properties 访问数据,使用 extension functions 进行有副作用的操作。

使用 var 和 setter 的可变 extension property

使用关键字 var 的 extension property 支持 setter,但无法存储值 — setter 通常执行副作用或将数据保存到外部存储。语法 与类的可变属性类似。

kotlin
// 带 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 的实际示例

在实际项目中,extension properties 最常用于简化集合数据的访问、计算 UI 元素的大小或状态以及在现有类之上创建方便的 API。Kotlin 标准库 积极使用这种机制:size、indices、lastIndex — 这些都是 extension properties。

kotlin
// 集合的 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。它不仅更简短,而且读起来像自然语言:“如果视图可见”。尽管简单,这样的属性显著提高了代码的可读性。

常见问题

可以为 companion object 声明 extension property 吗?

不可以,extension properties 不能为 companion object 或 object declaration 声明。Extension 机制仅适用于类、接口和可空类型。对于 object,请使用普通的顶层函数。

Extension property 和 inline 属性有什么区别?

Inline 属性(使用 inline 修饰符)— 是 Kotlin 的一种机制,用于调用 getter/setter 而不创建属性对象。Extension property 始终被编译为静态方法,而 inline 属性被编译为无包装的调用。它们解决不同的问题:extension property 向现有类型添加属性,inline 优化自己属性的调用。

Extension property 支持注解吗?

支持,extension property 可以包含注解,但仅在声明层面。与普通类属性不同,无法单独注解 extension property 的 getter 或 setter。示例:@JvmName(“getIsValid”) val String.isValid get() = true。

可以与类的 companion object 一起使用 extension property 吗?

不可以,extension properties 不能以 companion object 作为 receiver 声明。这是语言的限制 — extension property 仅与类型的实例一起工作,而 companion object 是静态上下文。使用顶层 extension functions 或常量。

Extension property 会影响 APK 大小吗?

微乎其微。每个 extension property 在编译后的字节码中添加一个静态 getter 方法(和可选的 setter)。相比之下,创建一个具有相同属性的封装类会添加整个类。Extension properties — 更轻量的功能扩展方法。

总结

  • Extension property — 为现有类型提供的计算属性,无需继承
  • 没有 backing field — 状态不存储,只通过 getter 计算
  • 带 setter 的 var — 可行,但需要外部存储来写入数据
  • 语法 — 带有 receiver 类型和必须的 getter 的 val/var
  • 性能 — 零开销,编译为静态方法
  • 应用 — 计算特征:长度、状态、大小、检查
  • 限制 — 不适合存储状态、缓存(无内存管理)

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读