您的位置:首页 > 其它

深入Kotlin - 专项 - 委托属性-1

2017-09-01 20:56 183 查看

委托属性

最常见的一类属性就是简单地从幕后字段中读取(以及可能的写入)。默认的

另一种,使用自定义getter和setter可以实现属性的任何行为。

而介于两者之间的,属性又有哪些常见的模式呢?

惰性值、通过键值从映射读取、访问数据库、访问通知侦听器等等。

下面我们将依次来看这些特殊的模式:首先我们来了解一下和委托相关的内容

类委托

委托模式已经被证明是实现继承的一个很好的代替方式,而Kotlin可以零样板代码地原生支持它。具体地用代码说话(哈哈)

interface Base{
fun print()
}

class BaseImpl(val x:Int):Base{
override fun print() {
println(x)
}
}

class Derived(b:Base):Base by b

fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print()
}


运行结果:输出10

这里我们看看Derived的实现和我们通常看到地不同:

它继承了接口Base但没有重写print()方法

使用了by关键字

最后的结果输出10,说明Derived最后调用的是b的print()方法

从上面可以看出:类Derived继承接口Base,并将其所有共有的方法通过by关键字委托给一个指定的对象b

注意这里b作为一个参数传入到了Dervied内部,具体细节我们可以通过查看字节码来看看:

public final class Derived implements Base {
// $FF: synthetic field
private final Base $$delegate_0;

public Derived(@NotNull Base b) {
Intrinsics.checkParameterIsNotNull(b, "b");
super();
this.$$delegate_0 = b;
}

public void print() {
this.$$delegate_0.print();
}
}


反编译之后我们可以清楚的看到:Kotlin自动为我们在Derived类内部声明了一个私有变量来保存b,从变量名我们就可以看出这保存的是委托(delegate)变量。不仅如此,Kotlin还自动为我们重写了print()方法,并且最终调用的是b的print()方法

结论:Derived 的超类型列表中的by-子句表示b将会在Derived中内部存储。并且编译器将生成转发给b的所有Base的方法。

用途:我们可以通过控制b来控制Derived实现不同的行为,例如:给老板配车,我们传入不同的车型,就可以开不同的车,且老板不用做任何修改,而且非常简洁。相反,在java中我们重复的多写好多代码

注:如果我们在Derived中override重写了print()方法,那么调用print()方法的时候最终将会调用Derived的print()方法,Kotlin编译器将不会再为我们生成print()方法。这就如同,老板配车但是司机固定一样。。。

关于类的委托到此就结束了,下面我们来看看属性的委托:

委托属性

有一些常见的属性类型,如:

延迟属性(lazy properties):其值只在首次访问时计算

可观察属性(observable properties):监听器会收到有关次属性变更的通知

把多个属性存储在一个映射(map)中,而不是每个存在单独的字段中

虽然我们可以每次在需要使用它们的时候动手去实现它们,那么这意味着同样的代码我们要去实现n次。。。(心累)

如果能有一个库能帮我们解决这些问题该多好呀,实际是不可能的,因为需求不同,情况太多了,一个库无法包含所有的情况。

为了涵盖这些(以及其他)情况,Kotlin支持委托属性。

语法:val/var \<属性名>: \<类型> by \<表达式>

by后面的表达式就是 委托,属性对应的 get() 和 set() 方法会被委托给它的getValue() 和 setValue()方法。

属性的委托不必实现任何接口,只需要提供一个getValue() 方法和 setValue()方法。

唉╮(╯▽╰)╭,这么说还是太抽象了,我们还是来看代码说话吧:

class Example{
var p:String by Delegate()
}

class Delegate {
operator fun getValue(example: Example, property: KProperty<*>): String {
return "$example, thank you for delegating '${property.name}' to me!"
}

operator fun setValue(example: Example, property: KProperty<*>, s: String) {
println("$s has been assigned to '${property.name}' in $example.'")
}
}

fun main(args: Array<String>) {
val e = Example()
e.p = "lxx"
println(e.p)
}


同样我们来看看字节码反编译的结果:

public final class Example {
@NotNull
private final Delegate p$delegate = new Delegate();
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Example.class), "p", "getP()Ljava/lang/String;"))};

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

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


还记得上面的类委托吗,是不是很相似,确实感觉差不多,Example内部也多了一个私有变量,不同的就是类委托中我们是直接委托给了一个对象(通过参数传入),而这里我们是new了一个对象,但这其实并没有影响,我们在类委托中同样可以这么做

有了这个方法,我们是不是很容易就可以实现对属性的控制了呢,而且是不是非常简洁,通过关键字by我们可以少写很多代码,而且非常明了。

不仅如此,Kotlin为了更好的帮助我们,Kotlin标准库中还提供了几种非常使用的委托工厂方法来方便我们使用。

1. 延迟属性Lazy

lazy()是接受一个lambda并返回一个Lazy\

class LazyValueDemo {
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
}

fun main(args: Array<String>) {
val ts = LazyValueDemo()
println(ts.lazyValue)
println(ts.lazyValue)
}


输出:

computed!

Hello

Hello

关于这段,暂时只是根据代码简单了解,还没有弄得很清楚::>_\<::

附录:

默认情况下,对于 lazy

属性的求值是同步锁的(synchronized):该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是

必需的,这样多个线程可以同时执行,那么将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy()

函数。而如果你确定初始化将总是 发生在单个线程,那么你可以使用 LazyThreadSafetyMode.NONE

模式,它不会有任何线程安全的保证和相关的开销。

2. 可观察属性 Observable

待补充,有些东西暂时没弄懂~~(>_\<)~~

3. 把属性存储在映射中 map

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Kotlin 委托属性