原创

Kotlin中的类委托和属性委托

Kotlin的委托可分为类委托和属性委托

一、类委托

类委托是代理模式的应用,而代理模式可以作为继承的一个不错的替代。

类委托的本质就是将本类需要实现的部分方法委托给其他对象——相当于借用其他对象的方法作为自己的实现。

package `0705`

import javax.swing.JOptionPane

interface ClassDelegation {
    fun output(msg: String)
    var type: String
}

//定义一个DefaultOutput类
class DefaultOutput : ClassDelegation {
    override fun output(msg: String) {
        for (i in 1..6) {
            println("<h${i}>${msg}</h${i}>")
        }
    }

    override var type: String = "输出设备"
}

//定义Printer类,指定构造参数b作为委托对象
class Printer(b: DefaultOutput) : ClassDelegation by b

//定义Projector类,指定新建的对象作为委托对象
class Projector() : ClassDelegation by DefaultOutput() {
    override fun output(msg: String) {
        JOptionPane.showMessageDialog(null, msg)
    }
}

fun main(args: Array<String>) {
    val output = DefaultOutput()
    //Printer对象的委托对象是output
    var printer = Printer(output)
    //其实就是调用委托对象的output()方法
    printer.output("kotlin")
    //其实就是调用委托对象的type属性方法
    println(printer.type)
    //Projector对象的委托对象也是output
    var projector = Projector()
    //Projrctor本身重写了output()方法
    projector.output("原来用Java")
    println(projector.type)
}

输出结果:

<h1>kotlin</h1>
<h2>kotlin</h2>
<h3>kotlin</h3>
<h4>kotlin</h4>
<h5>kotlin</h5>
<h6>kotlin</h6>
输出设备
输出设备

程序通过by关键字为类指定委托对象,这意味着这两个类可直接就”借用“被委托对象所实现的方法和属性。

当一个类重写了委托对象所包含的方法之后,Kotlin将优先使用该类自己实现的方法。

上例示范了两种为类指定委托对象的方式:

  • 通过构造器参数指定委托对象;
  • 直接在类定义的by后新建对象。

二、属性委托

Kotlin也支持属性委托,属性委托可以将多个类的类似属性统一交给委托对象集中实现,这样就可避免每个类都需要单独实现这些属性。

对于指定了委托对象的属性而言,由于它的实现逻辑已经交给委托对象处理,所以不能为委托属性提供getter和setter方法。


import kotlin.reflect.KProperty

class PropertyDelegation {
    //该属性的委托对象是MyDelegation
    var name: String by MyDelegation()
}

class MyDelegation {
    private var _backValue = "默认值"
    operator fun getValue(thisRef: PropertyDelegation, property: KProperty<*>): String {
        println("${thisRef}的${property.name}属性执行getter方法")
        return _backValue
    }

    operator fun setValue(thisRef: PropertyDelegation, property: KProperty<*>, newValue: String) {
        println("${thisRef}的${property.name}属性执行setter方法,传入参数值为${newValue}")
        _backValue = newValue
    }
}

fun main(args: Array<String>) {
    val pd = PropertyDelegation()
    //读取属性,实际上是调用属性的委托对象的getter方法
    println(pd.name)
    //写入属性,实际上是调用属性的委托对象的setter方法
    pd.name = "Kotlin"
    println(pd . name)
}

输出结果:

0708.PropertyDelegation@685cb137的name属性执行getter方法
默认值
0708.PropertyDelegation@685cb137的name属性执行setter方法,传入参数值为Kotlin
0708.PropertyDelegation@685cb137的name属性执行getter方法
Kotlin

Kotlin会委托属性生成辅助属性,该辅助属性引用了属性的委托对象。当程序调用委托属性的getter、setter方法时,实际上就是执行委托对象的getValue()、setValue()方法。

三、延迟属性

Kotlin提供了一个lazy()函数,该函数接受一个Lambda表达式作为参数,并返回一个Lazy对象。

Lazy对象包含了一个符合只读属性委托要求的getValue()方法,因此该Lazy对象可作为只读属性的委托对象。

val lazyProp: String by lazy {
    println("第一次访问时执行代码块")
    "Kotlin"
}

fun main(args: Array<String>) {
    //两次访问lazyProp属性
    println(lazyProp)
    println(lazyProp)
}

输出结果:

第一次访问时执行代码块
Kotlin
Kotlin

Lazy的getValue()方法的处理逻辑是:第一次调用该方法时,程序会计算Lambda表达式,并得到其返回值,以后程序再次调用该方法时,不再计算Lambda表达式,而是直接使用第一次计算得到的返回值。

lazy()函数有两个版本:

  • fun lazy(initializer: () -> T) : Lazy
  • fun lazy(mode:LazyThreadSafetyMode,initializer: () -> T):Lazy

上例使用的是第一个版本,该版本执行Lambda表达式时会提供自动添加保证线程安全的同步锁,因此开销略大。

第二个版本lazy()函数的第一个参数:

  • LazyThreadSafetyMode.SYNCHRONIZED:添加保证线程安全的同步锁。
  • LazyThreadSafetyMode.PUBLICATION:在这种模式下,lazy()函数不会使用排他同步锁,多个线程可同时执行。
  • LazyThreadSafetyMode.NONE:在这种模式下,lazy()函数不会有任何线程安全相关的操作和开销。

四、属性监听

Kotlin的kotlin.properties包下提供了一个Delegates对象声明(单例对象),该对象中包含两个常用方法:

  • observable:该方法返回一个ReadWriteProperty对象,该方法的返回值可作为只读属性的委托对象。
  • vetoable:该方法与上面方法基本相同,区别是负责执行监听的Lambda表达式需要返回值,当返回值为true时,接受委托的属性的新值设置成功;否则设置失败。

var observableProp: String by Delegates.observable("默认值") {
    //Lambda表达式的第一个参数代表被监听的属性
    //第二个参数代表修改之前的值
    //第三个参数代表被设置的新值
        prop, old, new ->
    println("${prop}的${old}被改为${new}")
}

fun main(args: Array<String>) {
    //访问observableProp属性,不会触发监听器的Lambda表达式
    println(observableProp)
    //设置属性值,触发监听器的Lambda表达式
    observableProp = "Kotlin"
    println(observableProp)
}

输出结果:

默认值
var observableProp: kotlin.String的默认值被改为Kotlin
Kotlin
var vetoableProp: Int by Delegates.vetoable(20) {
    //Lambda表达式的第一个参数代表被监听的属性
    //第二个参数代表修改之前的值
    //第三个参数代表被设置的新值
        prop, old, new ->
    println("${prop}的${old}被改为${new}")
    new > old
}

fun main(args: Array<String>) {
    //访问vetoableProp属性,不会触发监听器的Lambda表达式
    println(vetoableProp)
    //设置属性值小于旧值,触发监听器的Lambda表达式
    vetoableProp = 15
    println(vetoableProp)
    //设置属性值大于旧值,触发监听器的Lambda表达式
    vetoableProp = 25
    println(vetoableProp)
}

输出结果:

20
var vetoableProp: kotlin.Int的20被改为15
20
var vetoableProp: kotlin.Int的20被改为25
25

五、使用Map存储属性值

Kotlin的Map提供了如下方法:

  • operator fun <V,V1:V> Map<in String , V>.getValue(thisRef:Any?,property:KProperty<*>):V1:该方法符合只读属性的委托对象的要求,这意味着Map对象可作为只读对象的委托。

MutableMap提供如下两个方法:

  • operator fun <V,V1:V> Map<in String , V>.getValue(thisRef:Any?,property:KProperty<*>):V1
  • operator fun <V,V1:V> MutableMap<in String , V>.setValue(thisRef:Any?,property:KProperty<*>,value:V):V1
class Item(val map: Map<String, Any?>) {
    val barCode: String by map
    val name: String by map
    val price: Double by map
}

fun main(args: Array<String>) {
    val item = Item(
        mapOf(
            "barCode" to "12345679",
            "name" to "Kotlin",
            "price" to 10086
        )
    )
    println(item.barCode)
    println(item.name)
    println(item.price)
    println("/**************************")

    //将对象持有的map暴露出来
    val map = item.map
    println(map["barCode"])
    println(map["name"])
    println(map["price"])
}

输出结果:

12345679
Kotlin
10086.0
/**************************
12345679
Kotlin
10086

如果对象包含的属性是读写属性,则需要使用MutableMap作为委托对象。

class MutableItem(val map: MutableMap<String, Any?>) {
    var barCode: String by map
    var name: String by map
    var price: Double by map
}

fun main(args: Array<String>) {
    val item = MutableItem(mutableMapOf())
    //设置item对象的属性
    item.barCode = "12306"
    item.name = "Kotlin"
    item.price = 123.5
    println("********************************************")

    //将对象持有的map暴露出来
    val map = item.map
    println(map["barCode"])
    println(map["name"])
    println(map["price"])
}

输出结果:

********************************************
12306
Kotlin
123.5

六、局部属性委托

Kotlin从1.1开始支持为局部变量指定委托对象。


import kotlin.reflect.KProperty

class MyLocalDelegation {
    private var _backValue = "默认值"
    operator fun getValue(thisRef: Nothing?, property: KProperty<*>): String {
        println("${thisRef}的${property.name}属性执行getter方法")
        return _backValue
    }

    operator fun setValue(thisRef: Nothing?, property: KProperty<*>, newValue: String) {
        println("${thisRef}的${property.name}属性执行setter方法,传入参数值为:${newValue}")
        _backValue = newValue
    }
}

fun main(args: Array<String>) {
    var name: String by MyLocalDelegation()
    //访问局部变量,实际上是调用MyLocalDelegation对象的getValue()方法
    println(name)
    //对局部变量赋值,实际上是调用MyLocalDelegation对象的setValue()方法
    name = "新值"
    println(name)
}

输出结果:

null的name属性执行getter方法
默认值
null的name属性执行setter方法,传入参数值为:新值
null的name属性执行getter方法
新值

局部变量不属于任何对象。

定义一个延迟初始化的局部变量。

fun main(args: Array<String>) {
    val name: String by lazy {
        println("计算name局部变量")
        "kotlin "
    }

    //第一次访问name时,会执行Lambda表达式
    println(name)
    //以后访问name属性时,直接使用第一次计算的结果
    println(name)
}

输出结果:

计算name局部变量
kotlin 
kotlin 

七、委托工厂

从Kotlin1.1开始,一种类似于“委托工厂”的对象也可作为委托对象。

委托工厂需要提供如下方法:

  • operator fun provideDelegate(thisRef:Any? , prop:KProperty<*>):该方法与getValue()方法的两个参数的意义相同。
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

class MyTarget {
    //该属性的委托对象是provideDelegate()方法返回的MyDelegationItem对象
    var name: String by PropertyChecker()
}

class PropertyChecker() {
    operator fun provideDelegate(thisRef: MyTarget, prop: KProperty<*>): ReadWriteProperty<MyTarget, String> {
        //插入自定义代码,可执行任意业务操作
        checkProperty(thisRef, prop.name)
        //返回实际的委托对象
        return MyDelegationItem()
    }

    private fun checkProperty(thisRef: MyTarget, name: String) {
        println("-----------检查属性------------")
    }
}

class MyDelegationItem : ReadWriteProperty<MyTarget, String> {
    private var _backValue = "默认值"
    override operator fun getValue(thisRef: MyTarget, property: KProperty<*>): String {
        println("${thisRef}的${property.name}属性执行getter方法")
        return _backValue
    }

    override operator fun setValue(thisRef: MyTarget, property: KProperty<*>, value: String) {
        println("${thisRef}的${property.name}属性执行setter方法,传入参数值为:${value}")
        _backValue = value
    }
}

fun main(args: Array<String>) {
    //创建对象,调用委托工厂的provideDelegate
    val pd = MyTarget()
    //读取属性,实际上是调用属性的委托对象的getter方法
    println(pd.name)
    //写入属性,实际上是调用属性的委托对象的setter方法
    pd.name = "Kotlin"
    println(pd.name)
}

输出结果:

-----------检查属性------------
0708.MyTarget@5622fdf的name属性执行getter方法
默认值
0708.MyTarget@5622fdf的name属性执行setter方法,传入参数值为:Kotlin
0708.MyTarget@5622fdf的name属性执行getter方法
Kotlin

使用委托工厂的好处在于:程序可以在属性初始化时自动回调委托工厂的provideDelegate()方法,而该方法则可以执行自己的业务操作。

学海无涯苦作舟

我的微信公众号.jpg

基本语法
  • 作者:HunterArley (联系作者)
  • 发表时间:2019-11-26 10:48
  • 版权声明:本网站部分内容转载于合作站点或其他站点,但都会注明作/译者和原出处。如有不妥之处,敬请指出。
  • 公众号转载:请在文末添加作者公众号二维码