Kotlin Reference (十四) 委托类和委托属性

来源:互联网 发布:网络名字伤感两个字 编辑:程序博客网 时间:2024/05/17 17:42

KotLin 相关文档


官方在线Reference
kotlin-docs.pdf
Kotlin for android Developers 中文翻译
Kotlin开发工具集成,相关平台支持指南
Kotlin开源项目与Libraries
Kotlin开源项目、资源、书籍及课程搜索平台
Google’s sample projects written in Kotlin
Kotlin and Android

 

Kotlin委托机制


委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。

Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by 实现委托。

 

委托类 (Class Delegation)


例,

interface Base {    fun print()}class BaseImpl(val x: Int) : Base {    override fun print() {        print(x)    }}class Derived(b: Base) : Base by b {    fun add() {    }}fun main(args: Array<String>) {    val b = BaseImpl(10)    Derived(b).print() // prints 10    Derived(b).add()}

在基类列表上使用by子句,表示,Derived类的内部对象b,会生成Base的所有公共函数。这时调用Derived的属于Base的公共函数,就会委托调用b对象的对应函数。

Derived是一个委托类,其对应的委托实例就是构造函数中Base的实例b
 

委托属性 (Delegated Properties)


有一些常见的属性,虽然我们可以每次需要时手动实现它们,但现在只需要如下形式的使用,即可实现。这些属性,如:

  • lazy properties
    只在第一次访问时进行值计算

  • observable properties
    监听获取关于属性变化的通知

  • storing properties
    将属性存储在一个Map中
     

委托属性语法

例,

class Delegate {    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {        return "$thisRef, thank you for delegating '${property.name}' to me!"    }    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {        println("$value has been assigned to '${property.name} in $thisRef.'")    }}class Example {    var p: String by Delegate()}fun main(args: Array<String>) {    val example = Example()    example.p = "stone"    println(example.p)}

输出:

stone has been assigned to 'p in com.stone.clazzobj.delegation.Example@238e0d81.'com.stone.clazzobj.delegation.Example@238e0d81, thank you for delegating 'p' to me!

委托属性语法:
val/var <property name>: <Type> by <expression>
如,上例中的Example的属性p

这里将属性p的get/set委托给Delegate类的getValue/setValue函数来处理。

属性委托类,如Delegate类,其可以在形式上继承/实现一个基类/接口(这里指非特定的),然而该接口的函数在外部并不能由委托属性调用,所以继承/实现的特性,在这里并没有什么大的意义
 

标准委托属性

Kotlin标准库,为如下几种有用的委托形式,提供了相应的工厂方法

  • lazy
val lazyValue: String by lazy {    println("computed!") //多次调用  只会输出一次    "Hello"}

查看lazy源码,发现它调用的是:

public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

SynchronizedLazyImpl这是一个同步执行的函数,内部实现是加了synchronized的同步代码块的;所以在多线程环境中,第一个进入同步代码块执行出的结果,就是lazy-property的值。

再看lazy的一个重载方法:

public fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =        when (mode) {            LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)            LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)            LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)        }

使用,如

val lazyValue: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {...}

LazyThreadSafetyMode对应的三种模式:
a. SYNCHRONIZED 同步

b. PUBLICATION 发布。内部的计算过程可能被多次访问,但内部的属性是一个Volatile修饰的属性,所以在多线程环境中,被第一次访问获取数据后,此后的其它线程都共享该值。

c. NONE
未对多线程环境,做任何处理。
所以在多线程环境中使用,会造成:计算和返回值都可能处在多个线程中。

注:lazy属性,只能声明为 val的,即它是只能get的

 

  • Observable
    需要在属性后跟by Delegates.observable() ,即调用这个方法设定属性值。该方法是两个参数:初始值,lambda表达式(即处理值的语句块)。
    例,
import kotlin.properties.Delegatesclass User {    var name: String by Delegates.observable("<no name>") {        property, oldValue, newValue ->        println("$oldValue -> $newValue") }    }fun main(args: Array<String>) {     val user = User()     user.name = "first"     user.name = "second"}

输出:

<no name> -> firstfirst -> second

Delegates.observable()中,使用的lambda表达式,其参数:
I. property:在这指属性name
II. oldValue: 旧值,首次即为指定的初始值
III. newValue:新set的值
最终属性值=new值

还有个Delegates.vetoable(),跟Delegates.observable()类似,只是lambda表达式的返回值为Boolean。
返回true:属性值=new值
返回false:属性值=old值

observable 属性,可以定为val的,不过既然要监听属性的变化,还是用var较好

 

  • Storing Properties in a Map
    将属性委托给(存进)一个Map。
    例,
class Per(val map: Map<String, Any?>) {     val name: String by map    val age: Int by map}fun main(args: Array<String>) {     val per = Per(mapOf("name" to "John Doe", "age" to 25))    println(per.name)    println(per.age)}

输出:

John Doe25

也可以使用一个var map,即map的类型为MutableMap

 

局部委托属性

在kotlin1.1后,可以定义局部的委托属性。如,

class Foo {    fun isValid(): Boolean {        return Random().nextBoolean()    }    fun doSomething() {        println("doSomething")    }}fun example(computeFoo: () -> Foo) {    val memoizedFoo by lazy(computeFoo) //memoizedFoo: Foo    if (memoizedFoo.isValid()) {        memoizedFoo.doSomething()    }//  var p: String by Delegate() //可以有其它形式的委托属性}fun main(args: Array<String>) {    example {        println("生成Foo")        Foo()    }}

上例的example(),接受一个函数参数,该函数返回Foo类型对象。
memoizedFoo,就是一个局部委托属性;这里委托的是public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
这个函数,该lazy函数接受一个函数参数。
局部委托属性也可以是其它的委托属性形式。
 

委托属性给一个类实例之总结

  • 对于一个val修饰的只读属性

    它的委托必须提供一个名为getValue的函数。该函数中的两个参数的意义,如下
    thisRef : 其类型,必须是委托属性的所有者或其super类型
    property : 必须是KProperty<*>或其super类型 KCallable<*>

  • 对于一个var修饰的可变属性

    其含有参数,除了上述两个外,还多一个new-value值

注意getValue/setValue函数的声明以operator开始
这两个函数,可以直接定义成委托类的成员函数,或委托类的扩展函数。后者对于在原始类中未提供它们时,提供了方便

  • ReadOnlyProperty 和 ReadWriteProperty
    属性的委托类,可以实现两个接口,kotlin.properties.ReadOnlyPropertykotlin.properties.ReadWriteProperty;这两个接口中,声明了getValue(var和val属性)/setValue(var属性)函数。

    例,

class Example1: Super() {    val p1: String by Delegate1()}class Delegate1: ReadOnlyProperty<Example1, String> {    override fun getValue(thisRef: Example1, property: KProperty<*>): String {        return ""    }}

不实现这两个接口也是可以的,如之前使用 operator声明的getValue/setValue函数

 

委托属性转化规则

声明的委托属性,会由编译器产生一个隐藏属性,且会生成对应的get/set方法。

如,

class C {    var prop: Type by MyDelegate()}

在编译器中,会产生如下代码:

class C {    private val prop$delegate = MyDelegate()     var prop: Type        get() = prop$delegate.getValue(this, this::prop)        set(value: Type) = prop$delegate.setValue(this, this::prop, value)}

 

provideDelegate函数提供委托实例

在kotlin1.1,提供了provideDelegate函数,它可以为属性提供对象委托逻辑。
如果 by 右侧所使用的对象将 provideDelegate(函数名不能随意变更) 定义为成员或扩展函数,那么会调用该函数来 创建属性委托实例;且要求委托实例的类,要实现 ReadOnlyProperty 或 ReadWriteProperty接口

例,

class Dele(val id: String): ReadOnlyProperty<MyUI, String> {    override fun getValue(thisRef: MyUI, property: KProperty<*>): String {        return "${UUID.randomUUID()}_$id"    }}class ResourceID {    private constructor(id: String) {        this.id = id    }    open val id: String    companion object {        val image_id = ResourceID("1")        val text_id: ResourceID = ResourceID("2")    }}class ResourceLoader(resId: ResourceID) {    private val resourceID = resId    operator fun provideDelegate(thisRef: MyUI, prop: KProperty<*>): ReadOnlyProperty<MyUI, String> {        checkProperty(thisRef, prop.name)        // create delegate        return  Dele(resourceID.id+"")    }    private fun checkProperty(thisRef: MyUI, name: String) {        if (name.equals("image")) {            //...        }    }}fun MyUI.bindResource(id: ResourceID): ResourceLoader {    return ResourceLoader(id)}class MyUI {    val image by bindResource(ResourceID.image_id) //bindResource()产生委托对象    val text by bindResource(ResourceID.text_id)}fun main(args: Array<String>) {    println("image-id = " + MyUI().image)    println("txt-id = " + MyUI().text)}

上例中,找到by关键字,其右侧调用了bindResource创建委托类实例。
bindResource(这是一个扩展函数)返回ResourceLoader类型的实例。
ResourceLoader中定义了一个operator声明的函数,名为provideDelegate,其返回真正的委托类Dele的实例对象。
Dele中,getValue函数,返回一个”拼接了UUID前缀+资源类型id”的字符串。

 

使用provideDelegate函数的委托属性转化规则

class C {    var prop: Type by MyDelegate()}// this code is generated by the compiler// when the 'provideDelegate' function is available: class C {    // calling "provideDelegate" to create the additional "delegate" property    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)     val prop: Type}
原创粉丝点击