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 中非常有用的特性,它允许你:
- 扩展现有类:为任何类添加新属性
- 创建流畅 API:使代码更自然、易读
- 提供计算属性:创建基于现有数据的派生属性
- 支持 DSL:构建领域特定语言
注意事项:
- 扩展属性不存储状态,只能通过 getter/setter 计算
- 扩展属性不能有幕后字段
- 扩展属性是静态解析的,不支持多态
- 需要合理命名,避免与现有成员冲突
合理使用扩展属性可以显著提高代码的可读性和可维护性,尤其是在构建 DSL 或为常用类添加便捷属性时。