2-2-18 快速掌握Kotlin-扩展属性

Kotlin 语言中的扩展属性

扩展属性允许你为现有的类添加新的属性,而无需继承该类或修改其源代码。扩展属性不存储状态,它们通过 getter 和 setter 提供访问。

1. 基本语法

只读扩展属性(val)

kotlin 复制代码
// 为 String 类添加只读扩展属性
val String.firstChar: Char
    get() = this[0]

val String.lastChar: Char
    get() = this[length - 1]

val String.isPalindrome: Boolean
    get() = this == this.reversed()

// 使用
val text = "Kotlin"
println(text.firstChar)      // K
println(text.lastChar)       // n
println("level".isPalindrome) // true

可写扩展属性(var)

kotlin 复制代码
// 为 StringBuilder 添加可写扩展属性
var StringBuilder.firstChar: Char
    get() = this[0]
    set(value) {
        this.setCharAt(0, value)
    }

// 使用
val builder = StringBuilder("Hello")
builder.firstChar = 'J'
println(builder)  // Jello

2. 为常用类添加扩展属性

String 扩展属性

kotlin 复制代码
val String.wordCount: Int
    get() = this.split("\\s+".toRegex()).size

val String.camelCaseWords: List<String>
    get() = this.split("(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])".toRegex())

val String.isEmail: Boolean
    get() = this.matches(Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\$"))

// 使用
val text = "helloWorldExample"
println(text.camelCaseWords)  // [hello, World, Example]

List/Collection 扩展属性

kotlin 复制代码
val <T> List<T>.midElement: T?
    get() = if (isEmpty()) null else this[size / 2]

val <T> List<T>.second: T?
    get() = this.getOrNull(1)

val <T> List<T>.penultimate: T?
    get() = this.getOrNull(size - 2)

val <T> Collection<T>.isNotEmpty: Boolean
    get() = !isEmpty()

// 使用
val list = listOf(1, 2, 3, 4, 5)
println(list.midElement)    // 3
println(list.penultimate)   // 4

Number 扩展属性

kotlin 复制代码
val Int.isEven: Boolean
    get() = this % 2 == 0

val Int.isOdd: Boolean
    get() = this % 2 != 0

val Int.isPrime: Boolean
    get() {
        if (this <= 1) return false
        if (this <= 3) return true
        if (this % 2 == 0 || this % 3 == 0) return false
        var i = 5
        while (i * i <= this) {
            if (this % i == 0 || this % (i + 2) == 0) return false
            i += 6
        }
        return true
    }

val Double.formatted: String
    get() = "%.2f".format(this)

// 使用
println(7.isPrime)          // true
println(3.14159.formatted)  // 3.14

3. 可空类型的扩展属性

kotlin 复制代码
val String?.isNullOrEmptyOrBlank: Boolean
    get() = this == null || this.isBlank()

val String?.orDefault: String
    get() = this ?: "默认值"

val <T> T?.isNotNull: Boolean
    get() = this != null

val <T> T?.isNull: Boolean
    get() = this == null

// 使用
val nullableString: String? = null
println(nullableString.isNullOrEmptyOrBlank)  // true
println(nullableString.orDefault)             // 默认值

4. 泛型扩展属性

kotlin 复制代码
// 基本泛型扩展属性
val <T> List<T>.randomOrNull: T?
    get() = if (isEmpty()) null else this.random()

val <T> MutableList<T>.firstAndLast: Pair<T, T>?
    get() = if (size >= 2) Pair(first(), last()) else null

// 带类型约束的泛型扩展属性
val <T : Comparable<T>> List<T>.isSorted: Boolean
    get() = this.sorted() == this

val <T : Number> T.squared: Double
    get() = this.toDouble() * this.toDouble()

// 使用
val numbers = listOf(1, 2, 3, 4, 5)
println(numbers.randomOrNull)  // 随机元素
println(5.squared)             // 25.0

5. 为自定义类添加扩展属性

kotlin 复制代码
data class Person(val name: String, val age: Int)

// 为 Person 类添加扩展属性
val Person.isAdult: Boolean
    get() = age >= 18

val Person.initials: String
    get() = name.split(" ").map { it.first() }.joinToString("")

val Person.nameLength: Int
    get() = name.length

// 使用
val person = Person("John Doe", 25)
println(person.isAdult)   // true
println(person.initials)  // JD

6. 扩展属性与委托属性结合

kotlin 复制代码
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

// 创建扩展属性的委托
class UpperCaseDelegate : ReadOnlyProperty<Any?, String> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return property.name.uppercase()
    }
}

// 使用委托创建扩展属性
val Any?.upperCaseName: String by UpperCaseDelegate()

// 复杂的委托扩展属性
val String.characterFrequency: Map<Char, Int> by lazy {
    this.groupingBy { it }.eachCount()
}

// 使用
println("test".upperCaseName)  // UPPERCASENAME(属性名被转换为大写)
val freq = "hello".characterFrequency
println(freq)  // {h=1, e=1, l=2, o=1}

7. 实际项目中的应用

Android 开发

kotlin 复制代码
import android.view.View
import android.widget.TextView

// View 扩展属性
val View.isVisible: Boolean
    get() = visibility == View.VISIBLE

val View.isGone: Boolean
    get() = visibility == View.GONE

var TextView.textOrEmpty: String
    get() = text?.toString() ?: ""
    set(value) {
        text = value
    }

// SharedPreferences 扩展属性
val android.content.SharedPreferences.hasUserLoggedIn: Boolean
    get() = getBoolean("user_logged_in", false)

// 使用
if (button.isVisible) {
    button.isVisible = false
}

textView.textOrEmpty = "New Text"

日期时间处理

kotlin 复制代码
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

val LocalDate.isWeekend: Boolean
    get() = dayOfWeek.value in 6..7

val LocalDate.isWeekday: Boolean
    get() = !isWeekend

val LocalDate.isLeapYear: Boolean
    get() = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)

val LocalDateTime.formattedDate: String
    get() = format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))

// 使用
val today = LocalDate.now()
println(today.isWeekend)
println(today.formattedDate)

响应式编程

kotlin 复制代码
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow

// Flow 扩展属性
val <T> Flow<T>.latestValue: T?
    get() = null  // 实际实现需要特殊处理,这里只是示例

val <T> StateFlow<T>.valueOrNull: T?
    get() = try {
        value
    } catch (e: Exception) {
        null
    }

// 用于 Retrofit 响应的扩展属性
val <T> retrofit2.Response<T>.isSuccessfulAndNotNull: Boolean
    get() = isSuccessful && body() != null

val <T> retrofit2.Response<T>.errorMessage: String?
    get() = errorBody()?.string()

8. 操作符重载扩展属性

kotlin 复制代码
import kotlin.collections.component1
import kotlin.collections.component2

// 为 Pair 添加扩展属性,支持解构声明
operator fun Pair<Int, Int>.component3(): Int = first + second

val Pair<Int, Int>.sum: Int
    get() = first + second

val Pair<Int, Int>.product: Int
    get() = first * second

// 使用解构声明
val (x, y, sum) = Pair(3, 4)
println("$x + $y = $sum")  // 3 + 4 = 7

val pair = Pair(5, 6)
println(pair.product)  // 30

9. 扩展属性与扩展函数结合

kotlin 复制代码
// 扩展属性作为缓存
val String.cachedWords: List<String> by lazy {
    this.split("\\s+".toRegex())
}

// 扩展属性调用扩展函数
val String.reversedWords: String
    get() = this.split(" ").joinToString(" ") { it.reversed() }

// 计算密集型属性的缓存
val Int.factorial: Long
    get() {
        var result = 1L
        for (i in 2..this) {
            result *= i
        }
        return result
    }

// 使用
val text = "hello world"
println(text.cachedWords)    // [hello, world] (延迟计算)
println(text.reversedWords)  // olleh dlrow
println(5.factorial)         // 120

10. DSL 构建中的扩展属性

kotlin 复制代码
// HTML DSL 构建器
class HtmlBuilder {
    private val elements = mutableListOf<String>()
    
    operator fun String.unaryPlus() {
        elements.add(this)
    }
    
    override fun toString(): String = elements.joinToString("")
}

// 扩展属性用于 DSL
val HtmlBuilder.body: HtmlBuilder
    get() {
        +"<body>"
        return this
    }

val HtmlBuilder.endBody: HtmlBuilder
    get() {
        +"</body>"
        return this
    }

val HtmlBuilder.h1: String
    get() = "<h1>"

val HtmlBuilder.endH1: String
    get() = "</h1>"

// 使用 DSL
fun html(init: HtmlBuilder.() -> Unit): String {
    val builder = HtmlBuilder()
    builder.init()
    return builder.toString()
}

val result = html {
    +"<html>"
    body {
        +h1
        +"标题"
        +endH1
    }
    endBody
    +"</html>"
}

11. 性能考虑与最佳实践

性能考虑

kotlin 复制代码
// 避免在频繁调用的扩展属性中进行复杂计算
// 不好
val String.complexCalculation: Int
    get() {
        // 复杂计算
        return this.map { it.code }.sum()
    }

// 好:使用缓存或延迟计算
val String.cachedComplexCalculation: Int by lazy {
    this.map { it.code }.sum()
}

// 对于简单的计算,直接定义扩展属性
val String.lengthSquared: Int
    get() = length * length

最佳实践

kotlin 复制代码
// 1. 保持扩展属性简单
// 不好
val String.encodedAndCompressed: ByteArray
    get() {
        // 复杂的编码和压缩逻辑
        return byteArrayOf()
    }

// 好:拆分为多个简单属性或函数
val String.base64Encoded: String
    get() = java.util.Base64.getEncoder().encodeToString(toByteArray())

// 2. 提供有意义的名称
val Int.days: TimeSpan get() = TimeSpan(this, 0, 0, 0)
val Int.hours: TimeSpan get() = TimeSpan(0, this, 0, 0)

// 3. 避免与现有属性冲突
// 检查是否已有同名属性
// println(String::class.members.map { it.name })

12. 扩展属性的限制

kotlin 复制代码
// 1. 不能有幕后字段
// 错误示例
var String.cachedValue: String? = null  // 编译错误

// 2. 不能直接初始化
// 错误示例
val String.defaultValue: String = "default"  // 编译错误

// 3. 扩展属性不能访问私有成员
class PrivateClass {
    private val secret = "hidden"
}

// 错误:无法访问私有成员
// val PrivateClass.revealedSecret: String
//     get() = this.secret

// 4. 扩展属性是静态解析的
open class Animal
class Dog : Animal()

val Animal.type: String
    get() = "Animal"
val Dog.type: String
    get() = "Dog"

fun main() {
    val animal: Animal = Dog()
    println(animal.type)  // 输出: Animal(不是 Dog)
}

13. 工具函数与扩展属性的比较

kotlin 复制代码
// 工具函数方式
object StringUtils {
    fun getFirstChar(str: String): Char = str[0]
    fun isPalindrome(str: String): Boolean = str == str.reversed()
}

// 扩展属性方式
val String.firstChar: Char
    get() = this[0]
val String.isPalindrome: Boolean
    get() = this == this.reversed()

// 使用对比
val text = "level"

// 工具函数方式
println(StringUtils.getFirstChar(text))
println(StringUtils.isPalindrome(text))

// 扩展属性方式(更自然)
println(text.firstChar)
println(text.isPalindrome)

总结

扩展属性是 Kotlin 中非常有用的特性,它允许你:

  1. 扩展现有类:为任何类添加新属性
  2. 创建流畅 API:使代码更自然、易读
  3. 提供计算属性:创建基于现有数据的派生属性
  4. 支持 DSL:构建领域特定语言

注意事项:

  • 扩展属性不存储状态,只能通过 getter/setter 计算
  • 扩展属性不能有幕后字段
  • 扩展属性是静态解析的,不支持多态
  • 需要合理命名,避免与现有成员冲突

合理使用扩展属性可以显著提高代码的可读性和可维护性,尤其是在构建 DSL 或为常用类添加便捷属性时。

相关推荐
成都大菠萝4 小时前
2-2-21 快速掌握Kotlin-定义扩展文件
android
成都大菠萝4 小时前
2-2-19 快速掌握Kotlin-可空类型扩展函数
android
成都大菠萝4 小时前
2-2-23 快速掌握Kotlin-apply函数详解
android
2501_916007474 小时前
iOS 证书如何创建,从能生成到能长期使用
android·macos·ios·小程序·uni-app·cocoa·iphone
Just_Paranoid5 小时前
【AOSP】Android Dump 信息快速定位方法
android·adb·framework·service·aosp·dumpsys
帅得不敢出门5 小时前
MTK Android11获取真实wifi mac地址
android·mtk
成都大菠萝5 小时前
2-2-16 快速掌握Kotlin-泛型扩展函数
android
I'm Jie5 小时前
Gradle 多模块依赖集中管理方案,Version Catalogs 详解(Kotlin DSL)
android·java·spring boot·kotlin·gradle·maven
BoomHe5 小时前
Android 13 (API 33)开发自己的 Settings ,如何配置 WiFi BT 权限
android