kotlin-委托

[toc]

委托是什么

委托又可以稱為代理秉继。為其他對象提供一種代理以控制對這個對象的訪問,簡單的說就是在訪問和被訪問對象中間加上的一個間接層泽铛,以隔離訪問者和被訪問者的實現(xiàn)細節(jié)。

類委托

類委托其實對應于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()
}

輸出

我是真實的類

可以看到辑鲤,委托類(代理類)持有真實類的對象盔腔,然后委托類(代理類)調(diào)用真實類的同名方法,最終真正實現(xiàn)的是方法的是真實類月褥,這其實就是代理模式

kotlin中委托實現(xiàn)借助于by關(guān)鍵字弛随,by關(guān)鍵字后面就是被委托類,可以是一個表達式

接口委托

如果接口有很多方法宁赤,子類就必須全部實現(xiàn)舀透。

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

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

    override fun b() {
    }

    override fun c() {
    }
}

kotlin可以找個對象代替你實現(xiàn)它,我們只需要實現(xiàn)需要的方法即可决左。

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

設(shè)計一個好玩的例子

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。這里用了代理佛猛,不需要實現(xiàn)全部的方法惑芭。只需要提供必要的方法。

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,所以當我們調(diào)用屬性a的get/set方法時候婴渡,會委托到Text的setValue/getValue上,上面的例子可以看出來,里面有幾個參數(shù)介紹一下:

thisRef:屬性的擁有者
property:對屬性的描述幻锁,是KProperty<*>類型或者父類
value:屬性的值

更加簡單的實現(xiàn)屬性委托

每次實現(xiàn)委托都要寫getValue/setValue方法,這就比較麻煩了,系統(tǒng)為我們提供了接口边臼,方便我們重寫這些方法,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):其值只在訪問時初始化哄尔,再次調(diào)用直接返回結(jié)果
可觀察屬性(observable properties):監(jiān)聽器會收到此屬性的變更通知
把多個屬性映射到(Map)中,而不是存在單個字段

延遲屬性lazy

lazy()接收一個lambda硼瓣,返回Lazy實例究飞,返回的實例可以作為實現(xiàn)延遲屬性的委托,僅在第一次調(diào)用屬性進行初始化

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

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

輸出

第一次調(diào)用初始化
aa
aa
aa

lazy委托參數(shù)

SYNCHRONIZED:添加同步鎖堂鲤,使lazy延遲初始化線程安全
PUBLICATION:初始化的lambda表達式亿傅,可以在同一時間多次調(diào)用,但是只有第一次的返回值作為初始化值
NONE:沒有同步鎖瘟栖,非線程安全
1. by lazy是通過屬性代理來實現(xiàn)的懶加載葵擎,只在第一次使用的時候才會去執(zhí)行表達式,并且只會執(zhí)行一次半哟。
2. by lazy默認是線程安全的酬滤,內(nèi)部通過雙重判斷鎖來保證只執(zhí)行一次代碼塊賦值
3. 當能夠確定不會發(fā)生在多線程中的時候签餐,可通過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方法最終是創(chuàng)建的Lazy<T>的實例年栓,這個實例也就是屬性委托的對象拆挥。

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

Lazy一共有三個子類,其中我們使用的第一個方法返回的是SynchronizedLazyImpl某抓,這是Lazy的其中一個實現(xiàn)纸兔。

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
            }
            
            // 未初始化的否副,執(zhí)行傳遞進來的lambda參數(shù)進行賦值
            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方法中,會去判斷是否已經(jīng)初始化過副编,若是沒有初始化负甸,則會調(diào)用initializer去進行初始化,賦值給_value痹届。若是已經(jīng)初始化過呻待,則會直接返回 _value的值。因此by lazy就能通過這種方式去實現(xiàn)懶加載队腐,并且只加載一次蚕捉。

從代碼中可以看出,SynchronizedLazyImpl是保證了線程安全的柴淘。是通過DCL方式來保證的安全迫淹,能夠確保在多線程下也只會執(zhí)行一次代碼塊。

但是我們說過为严,要實現(xiàn)可讀屬性委托敛熬,必須實現(xiàn)getValue方法。而在SynchronizedLazyImpl中第股,并沒有實現(xiàn)getValue方法应民,而是只有value屬性的get方法。這里猜測編譯器應該是對Lazy有特殊的處理,而通過實驗诲锹,實現(xiàn)了Lazy接口的對象確實可以作為只讀屬性的委托對象繁仁。而其他接口即使與Lazy一模一樣,實現(xiàn)它的對象也不能作為屬性委托對象归园。

可觀察屬性O(shè)bservable委托

可以觀察一個屬性的變化過程

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

當新值小于舊值,那么就不生效庸诱,可以看到第三次設(shè)的值是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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市偶翅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌碉渡,老刑警劉巖聚谁,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異滞诺,居然都是意外死亡形导,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門习霹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朵耕,“玉大人,你說我怎么就攤上這事淋叶⊙植埽” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵煞檩,是天一觀的道長处嫌。 經(jīng)常有香客問我,道長斟湃,這世上最難降的妖魔是什么熏迹? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮凝赛,結(jié)果婚禮上注暗,老公的妹妹穿的比我還像新娘。我一直安慰自己墓猎,他們只是感情好捆昏,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著毙沾,像睡著了一般屡立。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天膨俐,我揣著相機與錄音勇皇,去河邊找鬼。 笑死焚刺,一個胖子當著我的面吹牛敛摘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乳愉,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼兄淫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蔓姚?” 一聲冷哼從身側(cè)響起捕虽,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎坡脐,沒想到半個月后泄私,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡备闲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年晌端,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恬砂。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡咧纠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泻骤,到底是詐尸還是另有隱情漆羔,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布狱掂,位于F島的核電站钧椰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏符欠。R本人自食惡果不足惜嫡霞,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望希柿。 院中可真熱鬧诊沪,春花似錦、人聲如沸曾撤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽挤悉。三九已至渐裸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背昏鹃。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工尚氛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人洞渤。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓阅嘶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親载迄。 傳聞我的和親對象是個殘疾皇子讯柔,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 委托也叫代理,是一種可以以代理方式控制目標對象的訪問护昧,設(shè)計模式中成為-代理模式魂迄。 Java中,我們實現(xiàn)一個代理模式...
    h2coder閱讀 919評論 0 0
  • 一、對象表達式 要創(chuàng)建?個繼承自某個(或某些)類型的匿名類的對象: 如果超類型有?個構(gòu)造函數(shù)浴捆,則必須傳遞適當?shù)臉?gòu)造...
    漆先生閱讀 578評論 0 0
  • Kotlin-屬性委托深入詳解 學習一下屬性的委托(delegated property)蒜田,我們知道定義一個類的屬...
    蔣斌文閱讀 527評論 1 1
  • 前序 委托,對于很多Java開發(fā)者來說都會一面蒙蔽选泻,我也不例外冲粤。委托,維基百科的解釋是:有兩個對象參與處理同一個請...
    大棋17閱讀 3,702評論 0 5
  • 首先介紹下自己的背景: 我11年左右入市到現(xiàn)在,也差不多有4年時間窝撵,看過一些關(guān)于股票投資的書籍傀顾,對于巴菲特等股神的...
    瞎投資閱讀 5,724評論 3 8