<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                原文章出處:[會寫「18.dp」只是個入門——Kotlin 的擴展函數和擴展屬性(Extension Functions / Properties)](https://kaixue.io/kotlin-extensions/) ## 開始 Kotlin 有個特別好用的功能叫擴展,你可以**給已有的類去額外添加函數和屬性,而且既不需要改源碼也不需要寫子類**。這就是今天這個視頻的主題。另外很多人雖然會用擴展,但只會最基本的使用,比如就只用來寫個叫`dp`? 的擴展屬性來把 dp 值轉成像素值: ~~~kotlin val Float.dp get() = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics ) ... val RADIUS = 200f.dp ~~~ 稍微高級一點就不太行了,尤其是擴展函數和函數引用混在一起的時候就更是瞬間蒙圈。如果你有這樣的問題,本篇文章應該可以幫到你。 ## Java 的 Math.pow() 在 Java 里我們如果想做冪運算——也就是幾的幾次方——要用靜態方法`pow(a, n)` ~~~java Math.pow(2, 10); // 2 的 10 次方 ~~~ pow 這個詞你可能不認識,其實它不是個完整的詞,而是 power 的縮寫,power 就是乘方的意思。這個`pow(a, n)`? 方法是`Math`? 類的一個靜態方法,這類方法我們用得比較多的是`max()`? 和`min()` ~~~java Math.max(1, 2); // 2 Math.min(1, 2); // 1 ~~~ 比較兩個數的大小,用靜態方法很符合直覺;但是冪運算的話,靜態方法就不如成員方法來得更直觀了: ~~~java 2.pow(10); // 要是 Java 里能這樣寫就好了 ~~~ 但我們只能選擇靜態方法。為什么?很簡單,因為 Integer、Float、Double 這幾個類沒提供這個方法,所以我們只能用 Math 類的靜態方法。 ## Kotlin 的擴展函數 Float.pow() 在 Kotlin 里,我們用的不是 Java 的 Integer、Float、Double,而是另外幾個名字相同或相像的 Kotlin 自己新創造的類。這幾個類同樣沒有提供`pow()`? 這個函數,但好的是,我們**依然可以用看起來像是成員函數的方式來做冪運算**。 ~~~kotlin 2f.pow(10) // Kotlin 可以這么寫 ~~~ 為什么?**因為`Float.pow(n: Int)`? 是 Kotlin 給`Float`? 這個類增加的一個擴展函數**: ~~~kotlin // kotlin.util.MathJVM.kt public actual inline fun Float.pow(n: Int): Float = nativeMath.pow(this.toDouble(), n.toDouble()).toFloat() ~~~ **在聲明一個函數的時候在函數名的左邊寫個類名再加個點,你就能對這個類的對象調用這個函數了。這種函數就叫擴展函數**,Extension Functions。就好像你鉆到這個類的源碼里,改了它的代碼,給它增加了一個新的函數一樣。雖然事實上不是,但用起來基本一樣。具體區別我等會兒說。 這種用法給我們的開發帶來了極大的便利,我們可以用它來做很多事。 舉個例子? * 比如 pow() 吧? * 再比如,AndroidX 里有個東西叫 ViewModel 對吧?——很多人對 ViewModel 有很大誤解,竟然以為這是用來寫 MVVM 架構的——AndroidX 的 KTX 庫里有一個對于 ComponentActivity 類的擴展函數叫 viewModels(): ![](https://img.kancloud.cn/fb/71/fb71c111598033a6ea223c63cb2d4246_884x187.png) 只要引用了對應的 KTX 庫,在 Activity 里你可以直接就調用這個函數來很方便地初始化 ViewModel: ~~~kotlin class MainActivity : AppCompatActivity() { val model: MyViewModel by viewModels()//委托 ... } ~~~ 而不需要重寫 Activity 類,上面示例中還用了委托屬性——[屬性委托](https://developer.android.google.cn/kotlin/common-patterns?hl=zh_cn#delegate) * 類似的用法可以有很多很多,限制你的是你的想象力。所以**其實對于擴展函數,你更需要注意的是謹慎和克制:需要用了再用,而不要因為它很酷很方便就能用則用。因為這些方便的東西如果太多,就會變成對你和同事的打擾**。 ## 擴展函數的寫法 擴展函數寫在哪都可以,但寫的位置不同,作用域就也不同。所謂作用域就是說你能在哪些地方調用到它。 **最簡單的寫法就是把它寫成 Top Level 也就是頂層的,讓它不屬于任何類,這樣你就能在任何類里使用它**。這也和成員函數的作用域很像——哪里能用到這個類,哪里就能用到類里的這個函數: ~~~kotlin package com.rengwuxian fun String.method1(i: Int) { ... } ... "rengwuxian".method1(1) ~~~ 有一點要注意了:**這個函數屬于誰?屬于函數名左邊的類嗎?并不是的,它是個 Top-level Function,它誰也不屬于,或者說它只屬于它所在的 package**。 那它為什么可以被這個類的對象調用呢?——因為它在函數名的左邊呀!**在 Kotlin 里,當你給聲明的函數名左邊加上一個類名的時候,表示你要給這個函數限定一個 Receiver——直譯的話叫接收者,其實也就是哪個類的對象可以調用這個函數。雖然說你是個 Top-level Function,不屬于任何類——確切地說是,不是任何一個類的成員函數——但我要限制只有通過某個類的對象才能調用你。這就是擴展函數的本質**。 那這……和成員函數有什么區別嗎?這種奇怪又繞腦子的知識有什么用嗎? ## 成員擴展函數 除了寫成 Top Level 的,**擴展函數也可以寫在某個類里**: ~~~kotlin class Example { fun String.method2(i: Int) { ... } } ~~~ 然后**你就可以在這個類里調用這個函數,但必須使用那個前綴類的對象來調用它**: ~~~kotlin class Example { fun String.method2(i: Int) { ... } ... "rengwuxian".method2(1) // 可以調用 } ~~~ 看起來……有點奇怪了。這個函數這么寫,它到底是屬于誰的呀?屬于外部的類還是左邊前綴的類? 屬于誰?這個「屬于誰」其實有點模糊的,我需要問再明確點:它是誰的成員函數?當然是外部的類的成員函數了,因為它寫在它里面嘛,對吧?那**函數名左邊的是什么**?剛才我剛說過,**它是這個函數的 Receiver,對吧?也就是誰可以去調用它**。 所以它既是外部類的成員函數,又是前綴類的擴展函數。 **這種既是成員函數、又是擴展函數的函數,它們的用法跟 Top Level 的擴展函數一樣,只是由于它同時還是成員函數,所以只能在它所屬的類里面被調用,到了外面就不能用了**: ~~~kotlin class Example { fun String.method2(i: Int) { ... } ... "rengwuxian".method2(1) // 可以調用 } "rengwuxian".method2(1) // 類的外部不能調用 ~~~ 這個……也好理解吧?你**為什么要把擴展函數寫在類的里面?不就是為了讓它不要被外界看見造成污染嗎,是吧?** ## 指向擴展函數的引用 在之前 Lambda 那一期視頻里,我說過函數是可以使用雙冒號被指向的對吧: ~~~kotlin Int::toFloat ~~~ 我當時也講了,**其實指向的并不是函數本身,而是和函數等價的一個對象**,這也是為什么你可以對這個引用調用 invoke(),卻不能對函數本身調用: ~~~kotlin (Int::toFloat)(1) // 等價于 1.toFloat() Int::toFloat.invoke(1) // 等價于 1.toFloat() 1.toFloat.invoke() // 報錯 ~~~ 但是為了簡單起見,我們通常可以**把這個「指向和函數等價的對象的引用」稱作是「指向這個函數的引用」**,這個問題不大。那么我們基于這個叫法繼續說。 **普通函數可以被指向,擴展函數同樣也是可以被指向的**: ~~~kotlin fun String.method1(i: Int) { } ... String::method1 ~~~ **不過如果這個擴展函數不是 Top-Level 的,也就是說如果它是某個類的成員函數,它就不能被引用了**: ~~~kotlin class Extensions { fun String.method1(i: Int) { ... } ... String::method1 // 報錯 } ~~~ 為什么?你想啊,一個成員函數怎么引用:類名加雙冒號加函數名對吧?擴展函數呢?也是類名加雙冒號加函數名對吧?只不過這次是 Receiver 的類名。**那成員擴展函數呢?還用類名加雙冒號加函數名唄?但是……用誰的類名?是這個函數所屬的類名,還是它的 Receiver 的類名?這是有歧義的,所以 Kotlin 就干脆不許我們引用既是成員函數又是擴展函數的函數了,一了百了**。 同樣,跟普通函數的引用一樣,擴展函數的引用也可以被調用,直接調用或者用 invoke() 都可以,不過要記得把 Receiver 也就是接收者或者說調用者填成第一個參數: ~~~kotlin (String::method1)("rengwuxian", 1) String::method1.invoke("rengwuxian", 1) // 以上兩句都等價于: "rengwuxian".method1(1) ~~~ ### 把擴展函數的引用賦值給變量 同樣的,**擴展函數的引用也可以賦值給變量**: ~~~kotlin val a: String.(Int) -> Unit = String::method1 ~~~ 然后**你再拿著這個變量去調用,或者再次傳遞給別的變量,都是可以的**: ~~~kotlin "rengwuxian".a(1) a("rengwuxian", 1) a.invoke("rengwuxian", 1) ~~~ ### 有無 Receiver 的變量的互換 **另外大家可能會發現,當你拿著一個函數的引用去調用的時候,不管是一個普通的成員函數還是擴展函數,你都需要把 Receiver 也就是接收者或者調用者作為第一個參數填進去**。 ~~~kotlin (String::method1)("rengwuxian", 1) // 等價于 "rengwuxian".method1(1) (Int::toFloat)(1) // 等價于 1.toFloat() ~~~ **為什么?因為你拿到的是函數引用而不是調用者的對象**,所以沒辦法在左邊寫上調用者啊,是吧? 所以 Kotlin 要想支持讓我們拿著函數的引用去調用,就必須給個途徑讓我們提供調用者。那提供怎樣的途徑呢?最終 Kotlin 給我們的方案就是:**在這種調用方式下,增加一個函數參數,讓我們把第一個參數的位置填上調用者。這樣,我們就可以用函數的引用來調用成員函數和擴展函數了**。但同時,又有一個問題我不知道你們發現沒有: 既然有 Receiver 的函數可以以無 Receiver 的方式來調用,那……它可以**賦值給無 Receiver 的函數類型的變量**嗎? ~~~kotlin val b: (String, Int) -> Unit = String::method1 // 這樣可以嗎? ~~~ 答案是,可以的。**在 Kotlin 里,每一個有 Receiver 的函數——其實就是成員函數和擴展函數——它的引用都可以賦值給兩種不同的函數類型變量:一種是有 Receiver 的,一種是沒有 Receiver 的**: ~~~kotlin val a: String.(Int) -> Unit = String::method1 val b: (String, Int) -> Unit = String::method1 ~~~ 這兩種寫法都是合法的。為什么?因為有用啊,是吧?有什么用我剛講過,忘了的倒個帶。 而且同樣的,**這兩種類型的變量也可以互相賦值來進行轉換**: ~~~kotlin val a: String.(Int) -> Unit = String::method1 val b: (String, Int) -> Unit = String::method1 val c: String.(Int) -> Unit = b val d: (String, Int) -> Unit = a ~~~ 既然這兩種類型的變量可以互相賦值來轉換,那不就是說無 Receiver 的函數引用也可以賦值給有 Receiver 的變量? 這樣的話,是不是**一個普通的無 Receiver 的函數也可以直接賦值給有 Receiver 的變量**? ~~~kotlin fun method3(s: String, i: Int) { } ... val e: (String, Int) -> Unit = ::method3 val f: String.(Int) -> Unit = ::method3 // 這種寫法也行哦 ~~~ 是的,這樣賦值也是可以的。 **通過這些類型的互相轉換,你可以把一個本來沒有 Receiver 的函數變得可以通過 Receiver 來調用**: ~~~kotlin fun method3(s: String, i: Int) { } ... val f: String.(Int) -> Unit = ::method3 "rengwuxian".method3(1) // 不允許調用,報錯 "rengwuxian".f(1) // 可以調用 ~~~ 這就很爽了哈? 當然了你也可以反向操作,去把一個有 Receiver 的函數變得不能用 Receiver 調用: ~~~kotlin fun String.method1(i: Int) { } ... val b: (String, Int) -> Unit = String::method1 "rengwuxian".method1(1) // 可以調用 "rengwuxian".b(1) // 不允許調用,報錯 ~~~ 這樣收窄功能好像沒什么用哈?不過我還是要把這個告訴你,因為這樣你的知識體系才是完整的。 ## 擴展屬性 除了擴展函數,**Kotlin 的擴展還包括擴展屬性**。它跟擴展函數是一個邏輯,就是**在聲明的屬性左邊寫上類名加點,這就是一個擴展屬性了**,英文原名叫 Extension Property。 ~~~kotlin val Float.dp get() = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics ) ... val RADIUS = 200f.dp ~~~ **它的用法和擴展函數一樣,但少了擴展函數在引用上以及 Receiver 上的一些比較繞的問題**,所以很簡單,你自己去研究吧。**有些東西寫成擴展屬性是比擴展函數要更加直觀和方便的**,所以雖然它很簡單,但研究一下絕對有好處。 ## 總結 這次講的內容挺多的,但其實也很簡單,主要就這么幾點:擴展函數、擴展函數的引用、有無 Receiver 的函數類型的轉換以及擴展屬性。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看