跳到主要内容

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 委托的核心内容:

  1. 类委托:使用 by 关键字将接口实现委托给其他对象,支持组合优于继承
  2. 属性委托:将属性的访问逻辑委托给其他对象
  3. 标准委托lazyobservablevetoablenotNull
  4. 属性间委托:一个属性委托给另一个属性
  5. Map 委托:将属性存储在 Map 中
  6. 自定义委托:实现 getValue/setValue 操作符函数
  7. provideDelegate:拦截委托创建过程

委托是 Kotlin 的强大特性,可以减少样板代码,实现灵活的设计模式。

练习

  1. 使用 lazy 实现一个单例模式的配置管理器
  2. 使用 observable 实现一个简单的事件监听器
  3. 创建一个自定义委托,实现属性值的范围检查
  4. 使用 Map 委托解析一个简单的 JSON 字符串
  5. 使用类委托实现一个带缓存的数据访问层装饰器