kotlin-委托

[toc]

委托是什么

委托又可以称为代理。为其他对象提供一种代理以控制对这个对象的访问,简单的说就是在访问和被访问对象中间加上的一个间接层,以隔离访问者和被访问者的实现细节。

类委托

类委托其实对应于Java中的代理模式

interface Base{
    fun text()
}

//被委托的类(真实的类)
class BaseImpl(val x :String ) : Base{
    override fun text() {
        println(x)
    }
}

//委托类(代理类)
class Devices (b :Base) :Base by b


fun main(){
    var b = BaseImpl("我是真实的类")
    Devices(b).text()
}

输出

我是真实的类

可以看到,委托类(代理类)持有真实类的对象,然后委托类(代理类)调用真实类的同名方法,最终真正实现的是方法的是真实类,这其实就是代理模式

kotlin中委托实现借助于by关键字,by关键字后面就是被委托类,可以是一个表达式

接口委托

如果接口有很多方法,子类就必须全部实现。

interface Api{
    fun a()
    fun b()
    fun c()
}

class ApiWrapper:Api{
    override fun a() {
    }

    override fun b() {
    }

    override fun c() {
    }
}

kotlin可以找个对象代替你实现它,我们只需要实现需要的方法即可。

class ApiWrapper(val api:Api):Api by api{
    override fun a() {
        println("判断是否登录")
        api.a()
    }
}

设计一个好玩的例子

class SupperArray<T>(val list: MutableList<T> = mutableListOf(), val map: MutableMap<String, T> = mutableMapOf()) :
    MutableList<T> by list, MutableMap<String, T> by map{

    override fun clear() {
        list.clear()
        map.clear()
    }

    override fun isEmpty(): Boolean {
        return list.isEmpty() && map.isEmpty()
    }

    override val size: Int
        get() = list.size+map.size

    override fun toString(): String {
        return "$list  $map"
    }
}

这是一个超级集合,包含一个list和一个map。这里用了代理,不需要实现全部的方法。只需要提供必要的方法。

fun main(args: Array<String>) {
    val supperArray = SupperArray<String>()
    supperArray["a"]= "hello"
    supperArray.add("world")
    println(supperArray.toString())
}

打印

[world]  {a=hello}

属性委托

属性委托和类委托一样,属性的委托其实是对属性的==set/get==方法的委托,把set/get方法委托给==setValue/getValue==方法,因此被委托类(真实类)需要提供==setValue/getValue==方法,val属性只需要提供==setValue==方法

属性委托语法:

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


class B {
    //委托属性
    var a : String by Text()
}

//被委托类(真实类)
class Text {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "属性拥有者 = $thisRef, 属性的名字 = '${property.name}' 属性的值 "
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("属性的值 = $value 属性的名字 =  '${property.name}' 属性拥有者 =  $thisRef")
    }
}

fun main(){
    var b = B()

    println(b.a)

    b.a = "ahaha"
    
}

输出

属性拥有者 = com.example.lib.weituo.B@27fa135a, 属性的名字 = 'a' 属性的值 
属性的值 = ahaha 属性的名字 =  'a' 属性拥有者 =  com.example.lib.weituo.B@27fa135a

上面的例子可以看到 ,属性a 委托给了Text,而且Text类中有setValue和getValue,所以当我们调用属性a的get/set方法时候,会委托到Text的setValue/getValue上,上面的例子可以看出来,里面有几个参数介绍一下:

thisRef:属性的拥有者
property:对属性的描述,是KProperty<*>类型或者父类
value:属性的值

更加简单的实现属性委托

每次实现委托都要写getValue/setValue方法,这就比较麻烦了,系统为我们提供了接口,方便我们重写这些方法,ReadOnlyProperty和ReadWriteProperty

interface ReadOnlyProperty<in R, out T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
}

interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

Koltin 标准库中提供的几个委托

延迟属性(lazy properties):其值只在访问时初始化,再次调用直接返回结果
可观察属性(observable properties):监听器会收到此属性的变更通知
把多个属性映射到(Map)中,而不是存在单个字段

延迟属性lazy

lazy()接收一个lambda,返回Lazy实例,返回的实例可以作为实现延迟属性的委托,仅在第一次调用属性进行初始化

class Lazy {
    val name :String by lazy {
        println("第一次调用初始化")
        "aa" }
}

fun main(){
    var lazy =Lazy()
    println(lazy.name)
    println(lazy.name)
    println(lazy.name)
}

输出

第一次调用初始化
aa
aa
aa

lazy委托参数

SYNCHRONIZED:添加同步锁,使lazy延迟初始化线程安全
PUBLICATION:初始化的lambda表达式,可以在同一时间多次调用,但是只有第一次的返回值作为初始化值
NONE:没有同步锁,非线程安全
1. by lazy是通过属性代理来实现的懒加载,只在第一次使用的时候才会去执行表达式,并且只会执行一次。
2. by lazy默认是线程安全的,内部通过双重判断锁来保证只执行一次代码块赋值
3. 当能够确定不会发生在多线程中的时候,可通过lazy(LazyThreadSafetyMode.NONE) { ... }来避免加锁。

lazy 原理

通过前面对属性委托的简单介绍,我们也明白了属性委托的机制。by后面跟着的肯定是一个对象,也就是委托对象,该对象负责属性的get/set,所以lazy这个方法最终肯定会返回一个对象。

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

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

lazy方法一共有三个,我们最常用的是第一个。从代码中可以看出,lazy方法最终是创建的Lazy<T>的实例,这个实例也就是属性委托的对象。

public interface Lazy<out T> {
    public val value: T
    public fun isInitialized(): Boolean
}

Lazy一共有三个子类,其中我们使用的第一个方法返回的是SynchronizedLazyImpl,这是Lazy的其中一个实现。

internal object UNINITIALIZED_VALUE

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    
    // 委托的属性的值由_value记录,初始值是单例对象UNINITIALIZED_VALUE
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this

    override val value: T
        get() {
            val _v1 = _value
            // 已初始化则直接返回对应值
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }
            
            // 未初始化的,执行传递进来的lambda参数进行赋值
            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    val typedValue = initializer!!()
                    _value = typedValue
                    initializer = null
                    typedValue
                }
            }
        }

    override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE

    override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

    private fun writeReplace(): Any = InitializedLazyImpl(value)
}

SynchronizedLazyImpl是Lazy的一个子类,可以看到在value属性的get方法中,会去判断是否已经初始化过,若是没有初始化,则会调用initializer去进行初始化,赋值给_value。若是已经初始化过,则会直接返回 _value的值。因此by lazy就能通过这种方式去实现懒加载,并且只加载一次。

从代码中可以看出,SynchronizedLazyImpl是保证了线程安全的。是通过DCL方式来保证的安全,能够确保在多线程下也只会执行一次代码块。

但是我们说过,要实现可读属性委托,必须实现getValue方法。而在SynchronizedLazyImpl中,并没有实现getValue方法,而是只有value属性的get方法。这里猜测编译器应该是对Lazy有特殊的处理,而通过实验,实现了Lazy接口的对象确实可以作为只读属性的委托对象。而其他接口即使与Lazy一模一样,实现它的对象也不能作为属性委托对象。

可观察属性Observable委托

可以观察一个属性的变化过程

class Lazy {
   
    var a : String by Delegates.observable("默认值"){
            property, oldValue, newValue ->

        println( "$oldValue -> $newValue ")
    }
}

fun main(){
    var lazy =Lazy()


    lazy.a = "第一次修改"

    lazy.a = "第二次修改"
}

输出

默认值 -> 第一次修改 
第一次修改 -> 第二次修改

vetoable委托

vetoable和Observable一样,可以观察属性的变化,不同的是vetoable可以决定是否使用新值

class C {
    var age: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
        println("oldValue = $oldValue -> oldValue = $newValue" )
        newValue > oldValue


    }
}

fun main() {
    var c = C()

    c.age = 5
    println(c.age)

    c.age = 10
    println(c.age)

    c.age = 8
    println(c.age)

    c.age = 20
    println(c.age)
}

输出

oldValue = 0 -> oldValue = 5
5
oldValue = 5 -> oldValue = 10
10
oldValue = 10 -> oldValue = 8
10
oldValue = 10 -> oldValue = 20
20

当新值小于旧值,那么就不生效,可以看到第三次设的值是8,小于10就没有生效

属性储存在Map中

class D(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
}

fun main(){
    var d = D(mapOf(
        "name" to "小明",
        "age" to 12
    ))

    println("name = ${d.name},age = ${d.age}")
}

输出

name = 小明,age = 12

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容