oynix

于无声处听惊雷,于无色处见繁花

Kotlin之属性代理

属性代理是Kotlin独有的功能。

1. 简介

属性代理是借助Kotlin中代理设计模式,把这个模式应用于一个属性时,它可以将属性访问器的逻辑代理给一个对象。即,将setter和getter的实现,都交给一个对象来实现,这个对象需要实现getValue和setValue方法,既可以是普通方法,也可以是扩展方法。使用属性时,依然照常使用,只是访问器会调用扶助对象的setValue和getValue。

1
2
3
4
5
6
7
8
class Foo {
var name: String by NameDelegate()
}

class NameDelegate {
operator fun <T> getValue(thisRef: Any?, property: KProperty<*>): T {}
operator fun <T> setValue(thisRef: Any?, property: KPproperty<*>, value: T) {}
}

上面便是属性代理的定义形式。另外,Kotlin还提供了几个常见的属性代理

1
2
3
4
Delegates.notNull()
Delegates.observable()
Delegates.vetoable()

2. 几个结构

在介绍Kotlin提供的代理之前,先要说2个它的接口,很好理解

  • ReadOnlyProperty,只读属性,只有getValue方法
    1
    2
    3
    public fun interface ReadOnlyProperty<in T, out V> {
    public operator fun getValue(thisRef: T, property: KProperty<*>): V
    }
  • ReadWriteProperty,可读写属性,既有getValue方法,也有setValue方法
    1
    2
    3
    4
    public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
    public override operator fun getValue(thisRef: T, property: KProperty<*>): V
    public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
    }

3. Delegates.notNull()

notNull是一个方法,返回的就是一个带有getValue和setValue方法的对象,其中主要代码如下

1
2
3
4
5
6
7
8
9
10
11
private class NotNullVar<T : Any>() : ReadWriteProperty<Any?, T> {
private var value: T? = null

public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
}

public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
}

可以看到,NotNullVal实现了可读写属性的接口,setValue没有变,只是赋值给成员变量,而getValue加了空判断。在非空问题上,Kotlin还提供了一个lateinit关键字,但是这个关键字只能用来修饰引用类型的变量,而notNull适用于基础数据类型和引用类型。

4. Delegates.observable()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class ObservableProperty<V>(initialValue: V) : ReadWriteProperty<Any?, V> {
private var value = initialValue
protected open fun beforeChange(property: KProperty<*>, oldValue: V, newValue: V): Boolean = true
protected open fun afterChange(property: KProperty<*>, oldValue: V, newValue: V): Unit {}

public override fun getValue(thisRef: Any?, property: KProperty<*>): V { return value }

public override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) {
val oldValue = this.value
if (!beforeChange(property, oldValue, value)) { return }
this.value = value
afterChange(property, oldValue, value)
}
}

public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
ReadWriteProperty<Any?, T> =
object : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
}

ObservableProperty,也是实现了可读写的接口,相比于可读写属性,多了两个方法,一个是beforeChange,一个是afterChange,若beforeChange返回值为false,那么此次set无效,返回true时方可生效,而afterChange则是在set成功后调用,beforeChange默认返回时true。

可以看到,observable提供了一个类型为ObservableProperty的对象,并重写了afterChange方法,交给了传入的onChange处理。也就是说,每次set完成后,都会调用传入onChange发出通知。

5. Delegates.vetoable()

1
2
3
4
5
public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean):
ReadWriteProperty<Any?, T> =
object : ObservableProperty<T>(initialValue) {
override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
}

看过上看的说明,这个就很容易理解了,重写了beforeChange,用传入的函数的返回值来决定本次set是否生效。

6. 原理

咋一看上去这种写法很高级,让咱们来编译成Java代码,看看这层层面纱之下,藏着的到底是什么。

1
2
3
4
class Teacher {
var name: String by Delegates.notNull()
var age: Int by Delegates.notNull()
}

编译成java之后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public final class Teacher {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Teacher.class), "name", "getName()Ljava/lang/String;")), (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Teacher.class), "age", "getAge()I"))};

@NotNull
private final ReadWriteProperty name$delegate;
@NotNull
private final ReadWriteProperty age$delegate;

@NotNull
public final String getName() {
return (String)this.name$delegate.getValue(this, $$delegatedProperties[0]);
}

public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name$delegate.setValue(this, $$delegatedProperties[0], var1);
}

public final int getAge() {
return ((Number)this.age$delegate.getValue(this, $$delegatedProperties[1])).intValue();
}

public final void setAge(int var1) {
this.age$delegate.setValue(this, $$delegatedProperties[1], var1);
}

public Teacher() {
this.name$delegate = Delegates.INSTANCE.notNull();
this.age$delegate = Delegates.INSTANCE.notNull();
}
}

分析

  • 不管是代理的setValue还是getValue,其中都有一个KProperty类型的参数,所以每个属性都需要一个对应的KProperty对象
  • 除了KProperty,每个属性,都需要一个代理对象,这里是ReadWriteProperty
  • 每个属性,都有一个setter,和一个getter
  • setter中,调用代理对象的setValue方法
  • getter中,调用代理对象的getValue方法

总结

我的感觉,如果能直接以最终编译后Java代码来看属性代理,它反倒是增加了不少代码,如果需要什么额外的操作,完全可以直接写在属性的setter或者getter里。语法糖的好处就是,可以写一个notNull的类型,然后在所有不为空的地方使用,可以不用在每一个地方都加上判断代码,提升了效率。

------------- (完) -------------
  • 本文作者: oynix
  • 本文链接: https://oynix.com/2022/05/e6c09fe4c2de/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道