2-2-38 快速掌握Kotlin-属性访问

25 阅读5分钟

Kotlin 语言的属性访问

Kotlin 中的属性访问是语言的核心特性之一,它提供了简洁、安全且强大的属性访问机制。

1. 基本属性访问

简单属性声明和访问

// 只读属性(val)
class Person {
    val name: String = "Alice"  // 必须在声明时或构造函数中初始化
    val age: Int = 25
    
    // 计算属性
    val isAdult: Boolean
        get() = age >= 18
}

// 可变属性(var)
class Counter {
    var count: Int = 0
    
    fun increment() {
        count++  // 直接访问属性
    }
}

// 使用
val person = Person()
println(person.name)      // 访问属性
println(person.isAdult)   // 访问计算属性

val counter = Counter()
counter.count = 10        // 设置属性
counter.increment()       // 内部访问属性

自动生成的 getter 和 setter

// Kotlin 会自动为属性生成 getter 和 setter
class User {
    var username: String = ""  // 自动生成 getUsername() 和 setUsername()
    var email: String = ""     // 自动生成 getEmail() 和 setEmail()
    
    // 属性可以有自定义的 getter/setter
    var age: Int = 0
        set(value) {
            field = value.coerceIn(0, 150)  // field 是幕后字段
        }
    
    var formattedEmail: String
        get() = email.uppercase()
        set(value) {
            email = value.lowercase()
        }
}

// 使用 - 看起来像直接访问字段,实际上调用的是 getter/setter
val user = User()
user.username = "john"     // 调用 setter
println(user.username)     // 调用 getter

2. 自定义 Getter 和 Setter

自定义 Getter

class Rectangle(val width: Double, val height: Double) {
    // 计算属性 - 没有幕后字段
    val area: Double
        get() = width * height
    
    val perimeter: Double
        get() = 2 * (width + height)
    
    val isSquare: Boolean
        get() = width == height
    
    // 带验证的计算属性
    val description: String
        get() = when {
            isSquare -> "正方形: ${width} x ${height}"
            else -> "矩形: ${width} x ${height}"
        }
}

// 使用
val rect = Rectangle(5.0, 3.0)
println("面积: ${rect.area}")
println("周长: ${rect.perimeter}")
println("是否正方形: ${rect.isSquare}")
println(rect.description)

自定义 Setter

class Temperature {
    // 使用幕后字段 field
    var celsius: Double = 0.0
        set(value) {
            field = value.coerceAtLeast(-273.15)  // 绝对零度
        }
    
    // 华氏度转换
    var fahrenheit: Double
        get() = celsius * 9 / 5 + 32
        set(value) {
            celsius = (value - 32) * 5 / 9
        }
    
    // 带日志的 setter
    var kelvin: Double = 0.0
        set(value) {
            println("温度从 $field K 更改为 $value K")
            field = value
        }
}

// 使用
val temp = Temperature()
temp.celsius = 25.0
println("摄氏: ${temp.celsius}°C")
println("华氏: ${temp.fahrenheit}°F")

temp.fahrenheit = 77.0
println("设置华氏后摄氏: ${temp.celsius}°C")

temp.kelvin = 300.0  // 会打印日志

带参数验证的 Setter

data class BankAccount(val accountNumber: String) {
    var balance: Double = 0.0
        set(value) {
            require(value >= 0) { "余额不能为负数" }
            field = value
        }
    
    var ownerName: String = ""
        set(value) {
            require(value.isNotBlank()) { "所有者姓名不能为空" }
            require(value.length >= 2) { "所有者姓名至少需要2个字符" }
            field = value.trim()
        }
    
    var interestRate: Double = 0.0
        set(value) {
            check(value in 0.0..20.0) { "利率必须在 0-20% 之间" }
            field = value
        }
}

// 使用
val account = BankAccount("123456")
try {
    account.balance = -100.0  // 抛出 IllegalArgumentException
} catch (e: IllegalArgumentException) {
    println(e.message)
}

account.ownerName = "张三"
println("账户所有者: ${account.ownerName}")

3. 幕后字段(Backing Field)

使用 field 关键字

class UserProfile {
    // 幕后字段的使用场景
    private var _username: String = ""
    
    var username: String
        get() = _username
        set(value) {
            _username = value.trim()
        }
    
    // 使用 field 关键字的等价写法
    var email: String = ""
        get() = field
        set(value) {
            field = value.lowercase()
        }
    
    // 使用 field 进行延迟初始化
    var age: Int = 0
        set(value) {
            println("年龄从 $field 岁变为 $value 岁")
            field = value
        }
    
    // 计算属性没有 field
    val isAdult: Boolean
        get() = age >= 18  // 这里不能使用 field
}

// 访问器中的 field 规则
class Example {
    var counter: Int = 0
        get() {
            println("访问计数器: $field")
            return field
        }
        set(value) {
            println("设置计数器: $field -> $value")
            field = value
        }
    
    // 错误示例:在 getter 中不能修改 field
    // var badExample: Int = 0
    //     get() {
    //         field++  // 编译错误:Val cannot be reassigned
    //         return field
    //     }
}

幕后字段 vs 幕后属性

// 方式1:使用幕后字段(简洁)
class Product1 {
    var price: Double = 0.0
        set(value) {
            field = value.coerceAtLeast(0.0)
        }
}

// 方式2:使用幕后属性(更灵活)
class Product2 {
    private var _price: Double = 0.0
    
    var price: Double
        get() = _price
        set(value) {
            _price = value.coerceAtLeast(0.0)
        }
    
    // 可以添加额外的方法
    fun applyDiscount(discountPercent: Double) {
        _price *= (1 - discountPercent / 100)
    }
}

// 选择建议:
// - 简单验证/转换:使用 field
// - 复杂逻辑/需要额外方法:使用幕后属性

4. 延迟初始化属性

lateinit 修饰符

class ServiceManager {
    // 必须使用 var,不能是原始类型,不能有自定义 getter/setter
    lateinit var databaseService: DatabaseService
    lateinit var networkService: NetworkService
    lateinit var configuration: Configuration
    
    fun initialize() {
        databaseService = DatabaseService()
        networkService = NetworkService()
        configuration = Configuration.load()
    }
    
    fun start() {
        // 在使用前检查是否已初始化
        if (::databaseService.isInitialized) {
            databaseService.connect()
        } else {
            throw IllegalStateException("databaseService 未初始化")
        }
    }
}

// 为测试提供的延迟初始化
class TestClass {
    lateinit var mockData: String
    
    fun setupTest() {
        mockData = "测试数据"
    }
    
    fun runTest() {
        // 安全的延迟初始化检查
        if (!::mockData.isInitialized) {
            setupTest()
        }
        println(mockData)
    }
}

// 实际应用:依赖注入
class UserController {
    lateinit var userService: UserService
    lateinit var authService: AuthService
    
    fun processRequest(userId: String) {
        // 框架会在运行时注入这些依赖
        val user = userService.findById(userId)
        authService.validate(user)
    }
}

by lazy 延迟初始化

class ResourceManager {
    // 延迟初始化,线程安全
    val expensiveResource: ExpensiveResource by lazy {
        println("初始化 expensiveResource")
        ExpensiveResource()
    }
    
    // 指定线程模式
    val configuration: Configuration by lazy(LazyThreadSafetyMode.NONE) {
        // 非线程安全,但初始化更快
        Configuration.load()
    }
    
    val userPreferences: Preferences by lazy {
        Preferences.loadFromFile()
    }
    
    // 复杂的延迟初始化
    val complexObject: ComplexObject by lazy {
        val config = configuration
        val prefs = userPreferences
        ComplexObject(config, prefs)
    }
    
    fun useResource() {
        // 第一次访问时初始化
        expensiveResource.doSomething()
        // 后续访问使用缓存的值
        expensiveResource.doSomething()
    }
}

// 单例模式使用 lazy
object DatabaseManager {
    val connectionPool: ConnectionPool by lazy {
        ConnectionPool.create(maxConnections = 10)
    }
    
    val queryCache: Cache by lazy {
        Cache.create(size = 1000)
    }
}

5. 委托属性(Property Delegation)

标准库委托

import kotlin.properties.Delegates

class ObservableExample {
    // 可观察属性
    var name: String by Delegates.observable("<未命名>") { property, oldValue, newValue ->
        println("${property.name} 从 '$oldValue' 变为 '$newValue'")
    }
    
    // 否决属性
    var age: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
        if (newValue < 0) {
            println("年龄不能为负数: $newValue")
            false  // 拒绝更改
        } else if (newValue > 150) {
            println("年龄不能超过150: $newValue")
            false  // 拒绝更改
        } else {
            true   // 接受更改
        }
    }
    
    // 非空属性,必须在使用前初始化
    var requiredField: String by Delegates.notNull()
    
    // 延迟属性(与 lateinit 类似,但适用于任何类型)
    var lazyField: String by Delegates.lazy {
        "延迟初始化的值"
    }
}

// 使用
val example = ObservableExample()
example.name = "Alice"  // 打印变更通知
example.age = 25        // 接受更改
example.age = -5        // 打印错误,拒绝更改

自定义委托属性

import kotlin.reflect.KProperty

// 1. 简单委托
class SimpleDelegate(private var value: String = "默认值") {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("读取 ${property.name}: $value")
        return value
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("设置 ${property.name}: $value")
        this.value = value
    }
}

// 2. Map 委托
class Configuration(properties: Map<String, Any>) {
    val host: String by properties
    val port: Int by properties
    val timeout: Long by properties
    val debug: Boolean by properties
}

// 3. 缓存委托
class CachedProperty<T>(private val initializer: () -> T) {
    private var cachedValue: T? = null
    private var initialized = false
    
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        if (!initialized) {
            cachedValue = initializer()
            initialized = true
            println("缓存 ${property.name}: $cachedValue")
        }
        return cachedValue!!
    }
}

// 4. 验证委托
class ValidatedProperty<T>(initialValue: T, private val validator: (T) -> Boolean) {
    private var value: T = initialValue
    
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
        if (validator(newValue)) {
            value = newValue
        } else {
            throw IllegalArgumentException("${property.name} 验证失败: $newValue")
        }
    }
}

// 使用自定义委托
class User {
    var description: String by SimpleDelegate()
    
    val expensiveComputation: String by CachedProperty {
        println("执行昂贵计算...")
        "计算结果"
    }
    
    var validatedAge: Int by ValidatedProperty(0) { it in 0..150 }
}

// 使用
val config = Configuration(mapOf(
    "host" to "localhost",
    "port" to 8080,
    "timeout" to 5000L,
    "debug" to true
))

println("Host: ${config.host}, Port: ${config.port}")

val user = User()
user.description = "新描述"
println(user.description)

println(user.expensiveComputation)  // 第一次访问会计算
println(user.expensiveComputation)  // 第二次访问使用缓存

6. 属性访问的可见性

可见性修饰符

class VisibilityExample {
    // 公开属性(默认)
    public var publicProperty: String = "公开"
    
    // 内部属性(同一模块内可见)
    internal var internalProperty: String = "内部"
    
    // 受保护属性(子类可见)
    protected var protectedProperty: String = "受保护"
    
    // 私有属性(仅本类可见)
    private var privateProperty: String = "私有"
    
    // 私有 setter,公开 getter
    var name: String = ""
        private set  // 只能在类内部修改
    
    var email: String = ""
        protected set  // 只能在类和子类中修改
    
    // 自定义可见性的 getter/setter
    var balance: Double = 0.0
        private get  // getter 是私有的
        public set    // setter 是公开的
    
    fun updateName(newName: String) {
        name = newName  // 可以在类内部修改
    }
}

class Subclass : VisibilityExample() {
    fun accessProperties() {
        // println(protectedProperty)  // 可以访问受保护属性
        // println(privateProperty)    // 编译错误:不能访问私有属性
        
        // email = "test@example.com"  // 可以设置,因为 setter 是 protected
    }
}

// 顶层属性可见性
private val privateTopLevel = "私有顶层"
internal val internalTopLevel = "内部顶层"
public val publicTopLevel = "公开顶层"  // public 是默认的,可以省略

7. 内联属性(Inline Properties)

// 内联属性扩展
inline val <T> List<T>.lastIndex: Int
    get() = size - 1

inline var StringBuilder.lastChar: Char
    get() = get(length - 1)
    set(value) {
        setCharAt(length - 1, value)
    }

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

val builder = StringBuilder("Hello")
builder.lastChar = '!'
println(builder)  // "Hell!"

8. 属性委托的实际应用

数据库字段映射

import java.util.*

// 数据库实体委托
class EntityDelegate<T>(private val fieldName: String, private val defaultValue: T) {
    private val values = mutableMapOf<String, Any>()
    
    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return values[fieldName] as? T ?: defaultValue
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        values[fieldName] = value as Any
    }
    
    fun getFieldValue(): Any? = values[fieldName]
}

// 使用委托的实体类
class UserEntity {
    var id: Long by EntityDelegate("id", 0L)
    var name: String by EntityDelegate("name", "")
    var email: String by EntityDelegate("email", "")
    var createdAt: Date by EntityDelegate("created_at", Date())
    var isActive: Boolean by EntityDelegate("is_active", true)
    
    fun toMap(): Map<String, Any> {
        return mapOf(
            "id" to id,
            "name" to name,
            "email" to email,
            "created_at" to createdAt,
            "is_active" to isActive
        )
    }
}

// 使用
val user = UserEntity()
user.id = 1L
user.name = "张三"
user.email = "zhangsan@example.com"
user.isActive = true

println(user.toMap())

配置管理

import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

// 配置委托
class ConfigDelegate(private val key: String, private val defaultValue: String) 
    : ReadOnlyProperty<Any, String> {
    
    private val config: Map<String, String> by lazy {
        loadConfigFromFile()
    }
    
    override fun getValue(thisRef: Any, property: KProperty<*>): String {
        return config[key] ?: defaultValue
    }
    
    private fun loadConfigFromFile(): Map<String, String> {
        // 从文件加载配置
        return mapOf(
            "app.name" to "My Application",
            "app.version" to "1.0.0",
            "api.url" to "https://api.example.com",
            "database.url" to "jdbc:mysql://localhost:3306/mydb"
        )
    }
}

// 配置类
class AppConfig {
    val appName: String by ConfigDelegate("app.name", "默认应用")
    val appVersion: String by ConfigDelegate("app.version", "1.0.0")
    val apiUrl: String by ConfigDelegate("api.url", "http://localhost:8080")
    val databaseUrl: String by ConfigDelegate("database.url", "")
}

// 使用
val config = AppConfig()
println("应用: ${config.appName} v${config.appVersion}")
println("API: ${config.apiUrl}")

9. 属性访问的最佳实践

// 1. 使用 val 除非确实需要可变
class GoodPractice {
    // 好:不可变属性
    val id: String = generateId()
    
    // 好:可变属性,但有验证
    var age: Int = 0
        set(value) {
            require(value >= 0) { "年龄不能为负数" }
            field = value
        }
    
    // 避免:公共可变属性没有保护
    var badPractice: String = ""  // 任何人都可以随意修改
    
    // 好:使用私有 setter
    var name: String = ""
        private set
    
    // 好:使用委托进行复杂逻辑
    var configValue: String by Delegates.observable("") { _, old, new ->
        println("配置从 '$old' 更改为 '$new'")
    }
}

// 2. 避免在 getter 中执行耗时操作
class PerformanceExample {
    // 不好:每次访问都执行数据库查询
    val userCount: Int
        get() = queryDatabaseForUserCount()  // 耗时操作
    
    // 好:使用缓存或 lazy
    val cachedUserCount: Int by lazy {
        queryDatabaseForUserCount()
    }
    
    private fun queryDatabaseForUserCount(): Int {
        // 模拟数据库查询
        Thread.sleep(1000)
        return 100
    }
}

// 3. 使用类型安全的属性访问
class TypeSafeExample {
    // 使用密封类或枚举来限制属性值
    enum class Status { ACTIVE, INACTIVE, PENDING }
    
    var status: Status = Status.PENDING
        set(value) {
            field = value
            // 状态变更时可以执行其他操作
            onStatusChanged(value)
        }
    
    private fun onStatusChanged(newStatus: Status) {
        println("状态更改为: $newStatus")
    }
}

总结

Kotlin 的属性访问提供了丰富的特性:

  1. 简洁性:看起来像字段访问,实际上是方法调用
  2. 安全性:支持验证、空安全和访问控制
  3. 灵活性:自定义 getter/setter、委托属性、延迟初始化
  4. 表达力:计算属性、扩展属性、内联属性

关键概念:

  • val:只读属性
  • var:可变属性
  • field:幕后字段引用
  • lateinit:延迟初始化(避免空检查)
  • by lazy:惰性初始化
  • by Delegates:标准库委托
  • 自定义委托:创建可重用的属性行为

通过合理使用这些特性,可以创建更安全、更简洁、更易维护的代码。