不理解是怎么实现的
//Derived 类可以通过将其所有公有成员都委托给指定对象来实现一个接口 Base:
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main() {
val base = BaseImpl(10)
Derived(base).print()//凭什么可以调用print(),就是by b
}
//Derived 的超类型列表中的 by-子句表示 b 将会在 Derived 中内部存储, 并且编译器将生成转发给 b 的所有 Base 的方法。
//语言特性——多态,父类引用可以指向子类对象。
从两个层面来讲:
类的委托
当我们使用by b这样的语法时,编译器会自动做两件事:
- 在
Derived类内部存储一个引用,指向b(也就是我们传入的Base实例)。 - 编译器会为
Derived类生成Base接口中所有方法(这里就是print()方法)的实现,这些生成的实现会转发给存储的b对象。也就是说,生成的Derived类的print()方法会调用b.print()。
如果没有这个语法糖,我们可能需要这样写:
class Derived(private val b: Base) : Base {
override fun print() {
b.print()
}
}
//相当于重复的模版代码,
但是使用by关键字,我们就可以省略这些样板代码,编译器会自动生成类似上面的代码。
因此,by关键字是Kotlin提供的一种语法糖,它通过编译器自动生成代码来简化委托模式的实现。这样,我们就可以将接口的实现委托给另一个对象,而不需要手动编写转发方法。
为什么可以这样实现?
编译器的实现原理
-
编译时处理:
by关键字告诉Kotlin编译器:- "这个类实现了Base接口"
- "但具体实现请委托给参数b"
-
生成字节码:编译器会生成实际的Java字节码,包含:
// 反编译后的Java代码类似:
public final class Derived implements Base {
private final Base delegate;
public Derived(Base b) {
this.delegate = b;
}
@Override
public void print() {
delegate.print(); // 自动生成的转发调用
}
}
验证例子
// 可以验证编译器生成的内容:
interface Base {
fun print()
fun show() // 添加第二个方法
}
class BaseImpl(val x: Int) : Base {
override fun print() { println("Print: $x") }
override fun show() { println("Show: $x") }
}
class Derived(b: Base) : Base by b // 一句话搞定!
fun main() {
val base = BaseImpl(10)
val derived = Derived(base)
derived.print() // 输出: Print: 10
derived.show() // 输出: Show: 10
// 两个方法都能正常工作,虽然Derived类中没有显式实现它们
}
这种设计的优势
1. 代码复用
// 多个类可以复用同一个实现
class Derived1(b: Base) : Base by b
class Derived2(b: Base) : Base by b
class Derived3(b: Base) : Base by b
// 无需重复编写相同的实现代码
2. 装饰器模式简化
class EnhancedDerived(b: Base) : Base by b {
// 可以只重写部分方法
override fun print() {
println("Before print")
b.print()
println("After print")
}
// show()方法会自动委托给b,无需重写
}
3. 运行时动态性
fun main() {
val impl1 = BaseImpl(10)
val impl2 = BaseImpl(20)
val derived = Derived(impl1)
derived.print() // 使用impl1
// 虽然不能直接改变delegate,但可以创建新实例
val derived2 = Derived(impl2) // 使用不同的实现
}
底层原理总结
| 阶段 | 发生什么 |
|---|---|
| 编写代码 | 只用写 : Base by b |
| 编译时 | 编译器检测到 by 关键字 |
| 代码生成 | 自动生成delegate字段和所有接口方法的转发实现 |
| 运行时 | 调用Derived的方法 → 转发给存储的delegate对象 |
所以"凭什么可以这样实现"的答案就是:这是Kotlin编译器提供的一种语法糖,它在编译阶段自动帮我们生成了委托模式所需的样板代码。
这类似于Java中的注解处理器或Lombok,但它是Kotlin语言内置的特性,更直接地集成在编译流程中。
属性的委托
为什么会出现?
对于某些常见的属性,虽然每次需要时都可以手动实现,但更方便的做法是一次性实现,将其添加到库中,以便日后重复使用。例如:
- 延迟属性(lazy properties): 其值只在首次访问时计算。
- 可观察属性(observable properties): 监听器会收到有关此属性变更的通知。
- 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
从官方语法说明看实现原理
委托属性(Delegated Property)
- 作用于类的单个属性,将属性的getter和setter委托给另一个对象(委托提供者)。
- 使用
by关键字在属性声明时指定委托对象。
语法是:
-
val/var <属性名>: <类型> by <表达式>。 -
在
by后面的表达式是该 委托, 因为属性对应的get()(与set())会被委托给它的getValue()与setValue()方法。 -
属性的委托不必实现接口,但是需要提供一个
getValue()函数(对于var属性还有setValue())。
委托属性的实现机制
- 编译时:编译器看到
by关键字,知道这是一个委托属性。 - 生成代码:编译器会生成一个委托对象实例,并将属性的getter和setter调用转发给该委托对象的
getValue和setValue方法。 - 运行时:当访问属性时,实际上调用的是委托对象的方法。
Kotlin 标准库中的委托属性
Kotlin 标准库提供了几个实用的委托属性实现:
1. lazy - 延迟初始化
class LazyDemo {
// 只有第一次访问时才计算值
val lazyValue: String by lazy {
println("计算中...")
"结果值"
}
}
fun main() {
val demo = LazyDemo()
println(demo.lazyValue) // 输出: 计算中... 结果值
println(demo.lazyValue) // 输出: 结果值 (不重新计算)
}
2. 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 = "Alice" // 输出: name 从 初始值 变为 Alice
user.name = "Bob" // 输出: name 从 Alice 变为 Bob
}
3. vetoable - 可否决的观察
import kotlin.properties.Delegates
class User {
var age: Int by Delegates.vetoable(0) {
property, oldValue, newValue ->
// 返回true接受新值,false拒绝
newValue >= 0 // 只允许非负年龄
}
}
fun main() {
val user = User()
user.age = 20 // 接受
println(user.age) // 20
user.age = -5 // 拒绝(回调返回false)
println(user.age) // 仍然是20
}
4. 将属性存储在Map中
class User(val map: Map<String, Any?>) {
// 属性值从map中读取
val name: String by map
val age: Int by map
}
fun main() {
val user = User(mapOf(
"name" to "John",
"age" to 25
))
println(user.name) // "John"
println(user.age) // 25
}
委托属性的底层原理
编译器做了什么?
// 源代码:
class Example {
var prop: Type by Delegate()
}
// 编译器生成的伪代码:
class Example {
// 1. 创建委托实例
private val prop$delegate = Delegate()
// 2. 生成属性的getter
var prop: Type
get() = prop$delegate.getValue(this, this::prop)
set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}
委托内必须实现的约定
// 对于只读属性(val):
class ReadOnlyDelegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, 感谢你委托属性 '${property.name}' 给我!"
}
}
// 对于读写属性(var):
class ReadWriteDelegate {
private var storedValue: String = ""
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return storedValue
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
storedValue = value
}
}
// 使用:
class Example {
val readOnly: String by ReadOnlyDelegate()
var readWrite: String by ReadWriteDelegate()
}
自定义委托属性的完整示例
import kotlin.reflect.KProperty
// 自定义委托:将属性值存储在SharedPreferences中
class PreferenceDelegate<T>(
private val context: Context,
private val key: String,
private val defaultValue: T
) {
private val prefs by lazy {
context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return when (defaultValue) {
is String -> prefs.getString(key, defaultValue as String) as T
is Int -> prefs.getInt(key, defaultValue as Int) as T
is Boolean -> prefs.getBoolean(key, defaultValue as Boolean) as T
else -> throw IllegalArgumentException("不支持的类型")
}
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
with(prefs.edit()) {
when (value) {
is String -> putString(key, value)
is Int -> putInt(key, value)
is Boolean -> putBoolean(key, value)
else -> throw IllegalArgumentException("不支持的类型")
}
apply()
}
}
}
// 使用:
class SettingsActivity : AppCompatActivity() {
// 自动保存到SharedPreferences
var userName: String by PreferenceDelegate(this, "user_name", "Guest")
var notificationsEnabled: Boolean by PreferenceDelegate(this, "notifications", true)
var fontSize: Int by PreferenceDelegate(this, "font_size", 16)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 读取(自动从SharedPreferences获取)
println("用户名: $userName")
// 写入(自动保存到SharedPreferences)
userName = "Alice"
fontSize = 18
}
}
委托属性的高级用法
组合多个委托
// 自定义委托:先记录日志,再调用原始委托
class LoggingDelegate<T>(private val delegate: ReadWriteDelegate) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
println("获取属性: ${property.name}")
return delegate.getValue(thisRef, property) as T
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
println("设置属性: ${property.name} = $value")
delegate.setValue(thisRef, property, value)
}
}
// 使用装饰器模式组合
class Example {
private val basicDelegate = ReadWriteDelegate()
var prop: String by LoggingDelegate(basicDelegate)
}
属性委托工厂
// 委托可以带参数,类似工厂模式
class ConfigurableDelegate(
private val logChanges: Boolean = true,
private val validate: (Any?) -> Boolean = { true }
) {
private var value: Any? = null
operator fun <T> getValue(thisRef: Any?, property: KProperty<*>): T {
@Suppress("UNCHECKED_CAST")
return value as T
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Any?) {
if (!validate(value)) {
throw IllegalArgumentException("无效的值: $value")
}
if (logChanges) {
println("${property.name} 变为: $value")
}
this.value = value
}
}
class User {
// 可配置的委托
var name: String by ConfigurableDelegate(
logChanges = true,
validate = { it is String && it.length >= 2 }
)
var age: Int by ConfigurableDelegate(
logChanges = false,
validate = { it is Int && it in 0..150 }
)
}
总结
委托属性与类委托的主要不同:
-
粒度不同:
- 类委托:整个接口/类的实现
- 属性委托:单个属性的访问逻辑
-
实现机制不同:
- 类委托:编译器为每个接口方法生成转发调用
- 属性委托:编译器生成
getValue()/setValue()调用
-
典型用途不同:
- 类委托:装饰器模式、代码复用
- 属性委托:延迟加载、属性观察、配置管理、数据绑定
-
灵活性不同:
- 属性委托更灵活,可以组合、配置、动态创建
委托属性是 Kotlin 中非常强大的特性,它让属性的访问逻辑可以像插件一样"插入",大大提高了代码的可复用性和可维护性。