Kotlin委托

Kotlin委托

1.Kotlin中的委托有两种形式,一种是委托模式,一种是委托属性。

2.委托模式是设计模式中的一种,它是有两个对象参与处理同意请求,接受请求的对象将请求委托给另一个对象来处理。Kotlin语法对于这种模式提供了一个简易的写法。

委托模式的好处是可以使用聚合来代替继承。
看一下代码,委托模式的写法为:

// 一个接口
interface Base {
    fun print()
}
// 第一个子类
class BaseImple1(val x:Int):Base {
    override fun print() { print(x) }
}
// 第二个子类
class BaseImple2(val baseImple1:BaseImple1) {
    fun print() { baseImple1.print() }
}
// 第三个子类
class BaseImple3(val baseImple1:BaseImple1) :Base {
    override fun print() { baseImple1.print(x) }
}

第二个子类和第三个子类都是委托了第一个子类来进行函数的处理的。

Kotlin针对于第三个子类的委托模式可以简化写法为:

// 第三个子类
class BaseImple3(val baseImple1:BaseImple1) :Base by baseImple1

对于BaseImple3对象的调用函数等行为,委托转发给了baseImple1此对象。

注意事项:

  • 规定必须要使用接口,而不能使用抽象类。
  • by关键字,委托的只是接口类中的函数,非接口中定义函数还是调用此对象所定义的函数和属性,不能委托。

3.委托属性

  • 延迟属性
  • 可观察属性
  • 多个属性保存在一个map

3.1 语法为:val/var <属性名>: <类型> by <表达式>,委托属性需要提供getValue()setValue()方法

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.")
    }
}

3.2 延迟属性
lazy()第一次调用会执行lambda表达式并且记录结果,后续调用只会返回结果记录。此函数是线程安全的,不需要在外部添加额外同步代码,也可以通过传入参数来指定,源码为:

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

三种加载模式:

  • SYNCHRONIZED,调用SynchronizedLazyImpl
  • PUBLICATION,调用SafePublicationLazyImpl
  • NONE,调用UnsafeLazyImpl

SynchronizedLazyImpl源码分析:

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @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
            }
            // 加锁
            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                } else {
                    // 第一次会走到这里执行lambda表达式,加结果保存到_value中,第二次则直接返回_value值,而不再执行lambda表达式中的内容
                    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)
}

内部有一个属性保存lambda表达式所执行的值,再次调用则直接返回,无需执行lambda

SafePublicationLazyImpl源码分析:

private class SafePublicationLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable {
    @Volatile private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // this final field is required to enable safe publication of constructed instance
    private val final: Any = UNINITIALIZED_VALUE

    override val value: T
        get() {
            val value = _value
            if (value !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return value as T
            }

            val initializerValue = initializer
            // if we see null in initializer here, it means that the value is already set by another thread
            // 第一次执行initializerValue,不回为null,则对value的值进行更新,initializer设置为null;
            if (initializerValue != null) {
                val newValue = initializerValue()
                if (valueUpdater.compareAndSet(this, UNINITIALIZED_VALUE, newValue)) {
                    initializer = null
                    return newValue
                }
            }
            @Suppress("UNCHECKED_CAST")
            return _value as T
        }

    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)

    companion object {
        private val valueUpdater = java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(
            SafePublicationLazyImpl::class.java,
            Any::class.java,
            "_value"
        )
    }
}

它是在不需要加锁,并发执行的情况下,可以通过此类来获取值,同lazy类似,都是第一次的时候执行lambda表达式,然后再次执行就直接通过此类中的属性返回值即可,区别只是是否加锁,可以在多个线程中执行。

UnsafeLazyImpl源码:

internal class UnsafeLazyImpl<out T>(initializer: () -> T) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    private var _value: Any? = UNINITIALIZED_VALUE

    override val value: T
        get() {
            if (_value === UNINITIALIZED_VALUE) {
                _value = initializer!!()
                initializer = null
            }
            @Suppress("UNCHECKED_CAST")
            return _value as T
        }

    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)
}

这个只是简单的进行了一下判断,当前若有值,则返回,没有的话就执行lambda保存表达式的值,跟锁等无关,最好在单线程中使用,要不然会有同步问题。

4.可观察属性

Delegates.observable()需要两个参数,一个是此值的初始值,一个是此属性被修改时lambda的处理程序。

Delegates.vetoable()需要参数同上一致,只不过是lambda的处理时机不一致,上面的是在属性修改之后,这个实在属性修改之前。

5.属性存储在map映射中

可以使用map来对类的属性保存,官网上的例子:

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

val user = User(mapOf(
    "name" to "John Doe",
    "age"  to 25
))

6.实际使用中的一些写法:

标准委托要自己写的话有ReadOnlyPropertyReadWriteProperty两个接口,也提供了Delegates类中的函数可以来进行委托,如果需要仿照此类写即可。
kotlinapplicationcontext的获取方式可以这么写:

class App : Application() {
    
    var instance by Delegates.notNull<Application>()
    
    override fun onCreate() {
        super.onCreate()
        instance = this
    }
}

延迟加载基本使用lazy就可满足需求,下面是在网上看到的一个Androidsp的使用方法,觉得不错,贴一下代码:

class Preference<T>(private val context: Context, private val name: String,
        private val default: T) {

    private val prefs: SharedPreferences by lazy {
        context.getSharedPreferences("default", Context.MODE_PRIVATE)
    }

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T = findPreference(name, default)

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        putPreference(name, value)
    }

    @Suppress("UNCHECKED_CAST")
    private fun findPreference(name: String, default: T): T = with(prefs) {
        val res: Any = when (default) {
            is Long -> getLong(name, default)
            is String -> getString(name, default)
            is Int -> getInt(name, default)
            is Boolean -> getBoolean(name, default)
            is Float -> getFloat(name, default)
            else -> throw IllegalArgumentException("This type can be saved into Preferences")
        }

        res as T
    }

    @SuppressLint("CommitPrefEdits")
    private fun putPreference(name: String, value: T) = with(prefs.edit()) {
        when (value) {
            is Long -> putLong(name, value)
            is String -> putString(name, value)
            is Int -> putInt(name, value)
            is Boolean -> putBoolean(name, value)
            is Float -> putFloat(name, value)
            else -> throw IllegalArgumentException("This type can't be saved into Preferences")
        }.apply()
    }
}
// 单例调用此函数
object DelegatesExt {
    fun <T> preference(context: Context, name: String,
            default: T) = Preference(context, name, default)

}
// 具体使用
private var zipCode: Long by DelegatesExt.preference(this, ZIP_CODE, DEFAULT_ZIP)

这个写法,就是使用了委托属性,然后操作属性即可,而不需要再像原来一样写一大堆的样板代码了。

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

推荐阅读更多精彩内容

  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,211评论 9 118
  • 本文是在学习和使用kotlin时的一些总结与体会,一些代码示例来自于网络或Kotlin官方文档,持续更新... 对...
    竹尘居士阅读 3,292评论 0 8
  • 面向对象编程(OOP) 在前面的章节中,我们学习了Kotlin的语言基础知识、类型系统、集合类以及泛型相关的知识。...
    Tenderness4阅读 4,444评论 1 6
  • 写在开头:本人打算开始写一个Kotlin系列的教程,一是使自己记忆和理解的更加深刻,二是可以分享给同样想学习Kot...
    胡奚冰阅读 1,249评论 0 6
  • 上午先生带孩子出去玩,我在家里没去。两个小时之后回来了,我在屋里听到声音没等敲门就把门打开了,宝宝一下子看到我了特...
    利萍阅读 131评论 0 0