91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Kotlin委托需要重視的哪些點

發布時間:2022-01-27 09:12:06 來源:億速云 閱讀:155 作者:iii 欄目:開發技術

這篇文章主要介紹“Kotlin委托需要重視的哪些點”,在日常操作中,相信很多人在Kotlin委托需要重視的哪些點問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Kotlin委托需要重視的哪些點”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

    前言

    委托模式是實現繼承的一個很好的替代方式,也是Kotlin語言的一種特性,可以很優雅的實現委托模式。在開發過程中也少不了使用它,但常常都會被低估。所以今天就讓它得到重視,去充分的掌握kotlin委托特性以及原理。

    一、委托類

    我們先完成一個委托類,常常用于實現類的委托模式,它的關鍵是通過by關鍵字:

    interface Base{
      fun print()
    }
    
    class BaseImpl(val x: Int): Base{
      override fun print() { print(x) }
    }
    
    class Derived(b: Base): Base by b
    
    fun main(){
      val b = BaseImpl(10)
      Deriived(b).print()
    }
    
    //最后輸出了10

    在這個委托模式中Derived相當于是個包裝,雖然也實現了base,但并不關心它怎么實現,通過by這個關鍵字,將接口的實現委托給了它的參數db。

    相當于Java代碼的結構:

    class Derived implements Base{
      Base b;
      public Derived(Base b){ this.b = b}
    }

    二、 委托屬性

    前面講到Kotlin委托類是委托的是接口方法,委托屬性委托的,則是屬性的getter和setter方法。kotlin支持的委托屬性語法:

    class Example {
        var prop: String by Delegate()
    }

    屬性對應的get()和set()會被委托給它的getValue和setValue方法。當然屬性的委托不是隨便寫的,對于與val屬性它必須要提供一個getValue函數,如果是var屬性的則要另外提供setValue屬性。先來看個官方提供的委托屬性Delegate:

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

    我們可以看到對于var修飾的屬性,必須要有getValue和setValue方法,同時這兩個方法必須有operator關鍵字的修飾。

    再來看第一個參數thisRef,它的類型是要這個屬性所有者的類型,或者是它的父類。當我們不確定屬性會屬于哪個類,就可以將thisRef的類型定義為Any?了。

    接著看另一個參數property,它的類型是必須要KProperty<*>或其超類型,它的值則是前面的字段的名字prop。

    最后一個參數,它的類型必須是委托屬性的類型,或者是它的父類。也就是說例子中的 value: String 也可以換成 value: Any。

    我們來測試下到底是不是這樣的:

    fun main() {
        println(Example().prop)
        Example().prop = "Hello, World"
    }

    則會看到輸出:

    Example@5197848c, thank you for delegating 'prop' to me!
    Hello, World has been assigned to 'prop' in Example@17f052a3.

    2.1 自定義委托

    在知道了委托屬性怎么寫之后,也可以根據需求來實現自己的屬性委托。但是每次寫都要寫那么多模板代碼,也是很麻煩的,所以官方也提供了接口類給我們快速實現:

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

    現在被委托的類只要實現這個接口的其中一個就可以了。對于 val 變量使用 ReadOnlyProperty,而 var 變量實現ReadWriteProperty。我們現在就用ReadWriteProperty 接口來實現一個自定義委托:

    class Owner {
      var text: String by StringDelegate()
    }
    
    
    class StringDelegate(private var s: String = "Hello"): ReadWriteProperty<Owner, String> {
        override operator fun getValue(thisRef: Owner, property: KProperty<*>): String {
            return s
        }
        override operator fun setValue(thisRef: Owner, property: KProperty<*>, value: String) {
            s = value
        }
    }

    三、委托進階

    3.1 懶加載委托

    懶加載委托,也就是我們再對一些資源進行操作的時候,希望它在被訪問的時候采取觸發,避免不必要的消耗。官方已經幫我們提供了一個lazy()方法來快速創建懶加載委托:

    val lazyData: String by lazy {
        request()
    }
    
    fun request(): String {
        println("執行網絡請求")
        return "網絡數據"
    }
    
    fun main() {
        println("開始")
        println(lazyData)
        println(lazyData)
    }
    
    //結果:
    開始
    執行網絡請求
    網絡數據
    網絡數據

    可以看到只有第一次調用,才會執行lambda表達式里的邏輯,后面再調用只會返回lambda表達式的最終結果。

    那么懶加載委托又是怎么實現的呢? 現在來看下它的源代碼:

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

    在這個里面lazy()方法會接收一個LazyThreadSafetyMod類型的參數,如果不傳這個參數的話,就會默認使用SynchronizedLazyImpl方式。看解釋就可以知道它是用來多線程同步的,而另外兩個則不是多線程安全的。

    • LazyThreadSafetyMode.PUBLICATION:初始化方法可以被多次調用,但是值只是第一次返回時的返回值,也就是只有第一次的返回值可以賦值給初始化的值。

    • LazyThreadSafetyMode. NONE:如果初始化將總是發生在與屬性使用位于相同的線程,這種情況下可以使用,但它沒有同步鎖。

    我們現在主要來看下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 {
                      //調用高級函數獲取其返回值
                        val typedValue = initializer!!()
                      //將返回值賦值給_value,用于下次判斷時,直接返回高級函數的返回值
                        _value = typedValue
                        initializer = null
                        typedValue
                    }
                }
            }
    			......
    }

    通過上面代碼,可以發現SynchronizedLazyImpl覆蓋了lazy接口的返回值,并且重寫了屬性的訪問器,具體邏輯是與Java的雙重校驗類似的。但Lazy接口又是怎么變成委托屬性的?

    在Lazy.kt文件中發現它聲明了Lazy接口的getValue擴展屬性,也就在最終賦值的時候會被調用,而我們在自定義委托中說過,對于val屬性,我們需要提供一個getValue函數。

    ## Lazy.kt
    //此擴展允許使用 Lazy 的實例進行屬性委托
    public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

    有了這個懶加載委托后,我們實現單例會變的更加簡單:

    class SingletonDemo private constructor() {
        companion object {
            val instance: SingletonDemo by lazy{
            SingletonDemo() }
        }
    }

    3.2 Delegates.observable 觀察者委托

    如果你要觀察一個屬性的變化過程,可以將屬性委托給Delegates.observable,它有三個參數:被賦值的屬性、舊值和新值:

    var name: String by Delegates.observable("<no name>") {
            prop, old, new ->
            println("$old -> $new")
        }

    返回了一個ObservableProperty 對象,繼承自 ReadWriteProperty。再來看下它的內部實現:

    public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
                ReadWriteProperty<Any?, T> =
            object : ObservableProperty<T>(initialValue) {
                override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
            }

    initialValue是初始值,而另外個參數onChange是屬性值被修改時的回調處理器。

    3.3 by map 映射委托

    一個常見的用例是在一個映射(map)里存儲屬性的值,它可以使用Map/MutableMap來實現屬性委托:

    class User(val map: Map<String, Any?>) {
        val name: String by map
    }
    
    fun main(args: Array<String>) {
        val map = mutableMapOf(
            "name" to "哈哈"
        )
        val user = User(map)
        println(user.name)
        map["name"] = "LOL"
        println(user.name)
    }
    
    //輸出:
    哈哈
    LoL

    不過在使用過程中會有個問題,如果Map中不存在委托屬性名的映射值的時候,會再取值時拋異常:Key $key is missing in the map:

    ## MapAccessors.kt
    public inline operator fun <V, V1 : V> MutableMap<in String, out @Exact V>.getValue(thisRef: Any?, property: KProperty<*>): V1 = (getOrImplicitDefault(property.name) as V1)
    
    @kotlin.internal.InlineOnly
    public inline operator fun <V> MutableMap<in String, in V>.setValue(thisRef: Any?, property: KProperty<*>, value: V) {
        this.put(property.name, value)
    }
    
    ## MapWithDefault.kt
    internal fun <K, V> Map<K, V>.getOrImplicitDefault(key: K): V {
        if (this is MapWithDefault)
            return this.getOrImplicitDefault(key)
    
        return getOrElseNullable(key, { throw NoSuchElementException("Key $key is missing in the map.") })
    }

    所以在使用的時候要注意,必須要有映射值。

    3.4 兩個屬性之間的直接委托

    從 Kotlin 1.4 開始,我們可以直接在語法層面將“屬性 A”委托給“屬性 B”,就如下示例:

    class Item {
        var count: Int = 0
        var total: Int by ::count
    }

    上面代碼total的值與count完全一致,因為我們把total這個屬性的getter和setter都委托給了count。可以用代碼來解釋下具體的邏輯:

    class Item {
        var count: Int = 0
    
        var total: Int
            get() = count
    
            set(value: Int) {
                count = value
            }
    }

    在寫法上,委托名稱可以使用":"限定符,比如this::delegate 或MyClass::delegate。

    這種用法在字段發生改變,又要保留原有的字段時非常有用。可以定義一個新字段,然后將其委托給原來的字段,這樣就不用擔心新老字段數值不一樣的問題了。

    3.5 提供委托

    如果要在綁定屬性委托之前再做一些額外的判斷工作要怎么辦?我們可以定義provideDelegate來實現:

    class StringDelegate(private var s: String = "Hello") {                                                     
        operator fun getValue(thisRef: Owner, property: KProperty<*>): String {
            return s
        }                       
        operator fun setValue(thisRef: Owner, property: KProperty<*>, value: String) {
                s = value
        }
    }
    
    
    class SmartDelegator {
    
        operator fun provideDelegate(
            thisRef: Owner,
            prop: KProperty<*>
        ): ReadWriteProperty<Owner, String> {
    	//根據屬性委托的名字傳入不同的初始值
            return if (prop.name.contains("log")) {
                StringDelegate("log")
            } else {
                StringDelegate("normal")
            }
        }
    }
    
    class Owner {
        var normalText: String by SmartDelegator()
        var logText: String by SmartDelegator()
    }
    
    fun main() {
        val owner = Owner()
        println(owner.normalText)
        println(owner.logText)
    }
    
    //結果:
    normal
    log

    這里我們創建了一個新的SmartDelegator,通過對成員方法provideDelegate再套了一層,然后在里面進行一些邏輯判斷,最后才把屬性委托getStringDelegate。

    這種攔截屬性與其委托之間的綁定的能力,大大縮短了要實現相同功能,還要必須傳遞屬性名的邏輯。

    四、委托栗子

    4.1 簡化Fragment / Activity 傳參

    平時在針對Fragment傳參,每次都要寫一大段代碼是不是很煩,現在有了委托這個法寶就來一起簡化它,正常模式如下:

    class BookDetailFragment : Fragment(R.layout.fragment_book_detail) {
    
        private var bookId: Int? = null
        private var bookType: Int? = null
    
        companion object {
    
            const val EXTRA_BOOK_ID = "bookId"
            const val EXTRA_BOOK_TYPE = "bookType";
    
            fun newInstance(bookId: Int, bookType: Int?) = BookDetailFragment().apply {
                Bundle().apply {
                    putInt(EXTRA_BOOK_ID, bookId)
                    if (null != bookType) {
                        putInt(EXTRA_BOOK_TYPE, bookType)
                    }
                }.also {
                    arguments = it
                }
            }
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            arguments?.let {
                bookId = it.getInt(EXTRA_book_ID, 123)
                bookType = it.getInt(EXTRA_BOOK_TYPE, 1)
            }
        }
    }

    寫了那么一大段,終于寫好了傳參的基本方法,在獲取值的時候還要處理參數為空的情況,現在我們就抽取委托類用屬性委托的方式重新實現上面功能:

    class BookDetailFragment : Fragment(R.layout.fragment_book_detail) {
    
        private var bookId: Int by argument()
    
        companion object {
            fun newInstance(bookId: Int, bookType: Int) = BookDetailFragment().apply {
                this.bookId = bookId
            }
        }
    
        override fun onViewCreated(root: View, savedInstanceState: Bundle?) {
          Log.d("tag", "BOOKID:" + bookId);
        }
    }

    看上去減少了大量代碼,是不是很神奇,下面實現思路如下所示:

    class FragmentArgumentProperty<T> : ReadWriteProperty<Fragment, T> {
    
        override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
          //對Bunndle取值還要進行單獨處理
            return thisRef.arguments?.getValue(property.name) as? T
                ?: throw IllegalStateException("Property ${property.name} could not be read")
        }
    
        override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) {
            val arguments = thisRef.arguments ?: Bundle().also { thisRef.arguments = it }
            if (arguments.containsKey(property.name)) {
                // The Value is not expected to be modified
                return
            }
          	//對Bunndle設值還要進行單獨處理
            arguments[property.name] = value
        }
    }
    
    fun <T> Fragment.argument(defaultValue: T? = null) = FragmentArgumentProperty(defaultValue)

    4.2 簡化SharedPreferences存取值

    如果我們現在存取值可以這樣做是不是很方便:

    private var spResponse: String by PreferenceString(SP_KEY_RESPONSE, "")
    
    // 讀取,展示緩存
    display(spResponse)
    
    // 更新緩存
    spResponse = response

    答案是可以的,還是用委托屬性來改造,下面就是具體的實現示例:

    class PreDelegate<T>(
            private val name: String,
            private val default: T,
            private val isCommit: Boolean = false,
            private val prefs: SharedPreferences = App.prefs) {
    
        operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
            return getPref(name, default) ?: default
        }
    
        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
            value?.let {
                putPref(name, value)
            }
        }
    
        private fun <T> getPref(name: String, default: T): T? = with(prefs) {
            val result: 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 is not supported")
            }
    
            result as? T
        }
    
        private fun <T> putPref(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 is not supported")
            }
    
            if (isCommit) {
                commit()
            } else {
                apply()
            }
        }
    }

    4.3 數據與View的綁定

    有了委托之后,在不用到DataBinding,數據與View之間也可以進行綁定。

    operator fun TextView.provideDelegate(value: Any?, property: KProperty<*>) = object : ReadWriteProperty<Any?, String?> {
        override fun getValue(thisRef: Any?, property: KProperty<*>): String? = text
        override fun setValue(thisRef: Any?, property: KProperty<*>, value: String?) {
            text = value
        }
    }

    給TextView寫一個擴展函數,讓它支持了String屬性的委托。

    val textView = findViewById<textView>(R.id.textView)
    
    var message: String? by textView
    
    textView.text = "Hello"
    println(message)
    
    message = "World"
    println(textView.text)
    
    //結果:
    Hello
    World

    我們通過委托的方式,將 message 委托給了 textView。這意味著,message 的 getter 和 setter 都將與 TextView 關聯到一起。

    到此,關于“Kotlin委托需要重視的哪些點”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    冕宁县| 承德市| 梅河口市| 景宁| 玉门市| 宁陵县| 社旗县| 五原县| 抚松县| 江口县| 永州市| 化德县| 黄梅县| 隆尧县| 南宁市| 鹤岗市| 唐山市| 延津县| 拉孜县| 休宁县| 肥东县| 墨竹工卡县| 铁力市| 焦作市| 平阳县| 萨迦县| 泽州县| 宿州市| 新巴尔虎左旗| 德江县| 乌兰浩特市| 木兰县| 建宁县| 宁都县| 安徽省| 杭州市| 时尚| 海南省| 茂名市| 友谊县| 蒙山县|