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 的属性访问提供了丰富的特性:
- 简洁性:看起来像字段访问,实际上是方法调用
- 安全性:支持验证、空安全和访问控制
- 灵活性:自定义 getter/setter、委托属性、延迟初始化
- 表达力:计算属性、扩展属性、内联属性
关键概念:
- val:只读属性
- var:可变属性
- field:幕后字段引用
- lateinit:延迟初始化(避免空检查)
- by lazy:惰性初始化
- by Delegates:标准库委托
- 自定义委托:创建可重用的属性行为
通过合理使用这些特性,可以创建更安全、更简洁、更易维护的代码。