Kotlin 委托
委托(Delegation)是一种设计模式,它允许一个对象将某些职责委托给另一个对象来处理。Kotlin 在语言层面原生支持委托模式,无需编写样板代码。委托分为两种主要类型:类委托和属性委托。
类委托
类委托允许一个类将接口的实现委托给另一个对象。这是一种组合优于继承的实现方式,避免了继承带来的耦合问题。
基本语法
使用 by 关键字将接口的实现委托给指定对象:
// 定义接口
interface Base {
fun printMessage()
fun printMessageLine()
}
// 实现接口的委托类
class BaseImpl(private val x: Int) : Base {
override fun printMessage() { print(x) }
override fun printMessageLine() { println(x) }
}
// 通过委托实现接口
class Derived(b: Base) : Base by b
fun main() {
val b = BaseImpl(10)
Derived(b).printMessage() // 输出: 10
}
工作原理:
by子句表示b将在Derived对象内部存储- 编译器自动生成
Base接口的所有方法,并将调用转发给b
覆盖委托成员
如果需要自定义某些方法的行为,可以覆盖它们:
interface Base {
fun printMessage()
fun printMessageLine()
}
class BaseImpl(private val x: Int) : Base {
override fun printMessage() { print(x) }
override fun printMessageLine() { println(x) }
}
class Derived(b: Base) : Base by b {
// 覆盖委托的方法
override fun printMessage() {
print("abc")
}
}
fun main() {
val b = BaseImpl(10)
Derived(b).printMessage() // 输出: abc(使用覆盖的实现)
}
注意:委托对象中调用的方法不会被覆盖影响:
interface Base {
val message: String
fun print()
}
class BaseImpl(override val message: String) : Base {
override fun print() { println(message) } // 委托对象中的方法
}
class Derived(b: Base) : Base by b {
// 这个覆盖不会影响委托对象中的 print() 方法
override val message = "Message of Derived"
}
fun main() {
val b = BaseImpl("Base message")
val derived = Derived(b)
derived.print() // 输出: Base message
println(derived.message) // 输出: Message of Derived
}
委托对象只能访问自己的实现,无法感知到派生类中的覆盖。
实际应用场景
装饰器模式:
interface Repository {
fun save(data: String)
fun load(id: Int): String
}
class DatabaseRepository : Repository {
override fun save(data: String) {
println("保存到数据库: $data")
}
override fun load(id: Int): String {
return "数据 #$id"
}
}
// 添加日志功能的装饰器
class LoggingRepository(
private val delegate: Repository
) : Repository by delegate {
override fun save(data: String) {
println("[LOG] 开始保存数据")
delegate.save(data)
println("[LOG] 数据保存完成")
}
}
fun main() {
val repo = LoggingRepository(DatabaseRepository())
repo.save("测试数据")
// [LOG] 开始保存数据
// 保存到数据库: 测试数据
// [LOG] 数据保存完成
}
属性委托
属性委托允许将属性的 getter 和 setter 委托给另一个对象处理。语法为 val/var <属性名>: <类型> by <表达式>。
委托的工作原理
委托对象必须提供 getValue() 和 setValue() 操作符函数:
import kotlin.reflect.KProperty
class Delegate {
// 用于 val(只读属性)
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, 感谢将 '${property.name}' 委托给我!"
}
// 用于 var(可变属性)
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("已将 $value 赋值给 '${property.name}' 在 $thisRef 中")
}
}
class Example {
var p: String by Delegate()
}
fun main() {
val e = Example()
println(e.p) // Example@..., 感谢将 'p' 委托给我!
e.p = "NEW" // 已将 NEW 赋值给 'p' 在 Example@... 中
}
参数说明:
thisRef:属性所属的对象(对于顶层属性为null)property:描述属性的 KProperty 对象value:要设置的新值(仅setValue)
标准委托
Kotlin 标准库提供了几种常用的委托工厂函数。
lazy - 延迟初始化
lazy() 函数接受一个 lambda,返回一个 Lazy<T> 实例。第一次访问属性时执行 lambda 并缓存结果:
val heavyData: String by lazy {
println("正在计算...")
Thread.sleep(1000) // 模拟耗时操作
"计算完成"
}
fun main() {
println("程序开始")
println(heavyData) // 第一次访问:正在计算... 计算完成
println(heavyData) // 第二次访问:直接返回缓存值
}
线程安全选项:
// 同步模式(默认):双重检查锁,线程安全
val syncLazy: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
"同步延迟初始化"
}
// 发布模式:多线程可能同时初始化,但只有一个结果被使用
val pubLazy: String by lazy(LazyThreadSafetyMode.PUBLICATION) {
"发布延迟初始化"
}
// 无锁模式:单线程环境使用,无同步开销
val noneLazy: String by lazy(LazyThreadSafetyMode.NONE) {
"无锁延迟初始化"
}
observable - 可观察属性
Delegates.observable() 在属性值改变后触发回调:
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("<无名字>") { property, oldValue, newValue ->
println("${property.name}: $oldValue -> $newValue")
}
}
fun main() {
val user = User()
user.name = "张三" // name: <无名字> -> 张三
user.name = "李四" // name: 张三 -> 李四
}
vetoable - 可否决属性
Delegates.vetoable() 在属性值改变前触发回调,可以拒绝修改:
import kotlin.properties.Delegates
class Person {
// 年龄必须是正数
var age: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
if (newValue < 0) {
println("年龄不能为负数: $newValue")
false // 拒绝修改
} else {
println("${property.name}: $oldValue -> $newValue")
true // 允许修改
}
}
}
fun main() {
val person = Person()
person.age = 25 // age: 0 -> 25
println(person.age) // 25
person.age = -5 // 年龄不能为负数: -5
println(person.age) // 25(修改被拒绝)
}
notNull - 非空委托
Delegates.notNull() 用于延迟初始化非空属性:
import kotlin.properties.Delegates
class Service {
// lateinit 的替代方案
var config: String by Delegates.notNull()
}
fun main() {
val service = Service()
// println(service.config) // 错误!未初始化
service.config = "配置信息"
println(service.config) // 配置信息
}
属性间委托
一个属性可以将其 getter 和 setter 委托给另一个属性:
class MyClass(var newName: Int = 0) {
// 将旧属性委托给新属性(向后兼容)
@Deprecated("使用 'newName' 代替", ReplaceWith("newName"))
var oldName: Int by this::newName
}
var topLevelInt: Int = 0
class AnotherClass {
// 委托给顶层属性
var delegatedToTopLevel: Int by ::topLevelInt
}
fun main() {
val myClass = MyClass()
myClass.oldName = 42
println(myClass.newName) // 42
val another = AnotherClass()
another.delegatedToTopLevel = 100
println(topLevelInt) // 100
}
使用场景:重构时保持 API 兼容性。
将属性存储在 Map 中
将 Map 作为属性的委托,常用于解析 JSON 等动态场景:
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
fun main() {
val user = User(mapOf(
"name" to "张三",
"age" to 25
))
println(user.name) // 张三
println(user.age) // 25
}
对于可变属性,使用 MutableMap:
class MutableUser(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
fun main() {
val user = MutableUser(mutableMapOf(
"name" to "张三",
"age" to 25
))
user.name = "李四"
println(user.map) // {name=李四, age=25}
}
局部委托属性
局部变量也可以使用委托:
fun process(computeData: () -> String) {
// 只在需要时计算
val data by lazy(computeData)
if (someCondition) {
// 只有执行到这里才会调用 computeData()
println(data)
}
}
自定义委托
实现委托接口
可以创建自定义委托类:
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
// 自定义日志委托
class LoggingDelegate<T>(private var value: T) : ReadWriteProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
println("获取 ${property.name} = $value")
return value
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
println("设置 ${property.name} = $value(原值: ${this.value})")
this.value = value
}
}
class Config {
var serverUrl: String by LoggingDelegate("localhost")
var port: Int by LoggingDelegate(8080)
}
fun main() {
val config = Config()
config.serverUrl = "example.com" // 设置 serverUrl = example.com(原值: localhost)
println(config.port) // 获取 port = 8080
}
使用 ReadOnlyProperty 和 ReadWriteProperty
标准库提供的接口简化委托创建:
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
// 使用匿名对象创建委托
fun <T> singleton(value: T) = ReadOnlyProperty<Any?, T> { _, property ->
println("访问属性: ${property.name}")
value
}
class Example {
val constant: String by singleton("固定值")
}
provideDelegate 操作符
通过定义 provideDelegate 操作符,可以拦截委托的创建过程:
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
// 检查属性名称的委托提供者
class ResourceLoader<T>(private val value: T) {
operator fun provideDelegate(
thisRef: Any?,
property: KProperty<*>
): ReadOnlyProperty<Any?, T> {
// 在创建委托时检查属性名
check(property.name.startsWith("valid")) {
"属性名必须以 'valid' 开头,当前是: ${property.name}"
}
return ReadOnlyProperty { _, _ -> value }
}
}
class MyConfig {
// 正确的属性名
val validUrl: String by ResourceLoader("https://example.com")
// 错误的属性名会导致异常
// val wrongName: String by ResourceLoader("value")
}
fun main() {
val config = MyConfig()
println(config.validUrl) // https://example.com
}
委托的最佳实践
选择合适的委托类型
| 场景 | 推荐委托 |
|---|---|
| 延迟初始化 | lazy |
| 属性变化监听 | observable |
| 条件性属性修改 | vetoable |
| 非空延迟初始化 | Delegates.notNull() 或 lateinit |
| 动态属性(JSON解析) | Map 委托 |
注意线程安全
// 多线程环境下使用同步模式
val sharedResource: ExpensiveResource by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
ExpensiveResource.create()
}
避免委托循环
// 错误:循环委托
class BadExample {
var a: Int by this::b
var b: Int by this::a // 循环!
}
理解性能影响
委托属性会生成额外的辅助属性和方法,在极端性能敏感场景需注意。
小结
本章介绍了 Kotlin 委托的核心内容:
- 类委托:使用
by关键字将接口实现委托给其他对象,支持组合优于继承 - 属性委托:将属性的访问逻辑委托给其他对象
- 标准委托:
lazy、observable、vetoable、notNull - 属性间委托:一个属性委托给另一个属性
- Map 委托:将属性存储在 Map 中
- 自定义委托:实现
getValue/setValue操作符函数 - provideDelegate:拦截委托创建过程
委托是 Kotlin 的强大特性,可以减少样板代码,实现灵活的设计模式。
练习
- 使用
lazy实现一个单例模式的配置管理器 - 使用
observable实现一个简单的事件监听器 - 创建一个自定义委托,实现属性值的范围检查
- 使用 Map 委托解析一个简单的 JSON 字符串
- 使用类委托实现一个带缓存的数据访问层装饰器