Kotlin难点解析:extension和this指针
擴展函數難點解析
大多數場景下,你都能輕松搞定Kotlin擴展。可是,看看下面這個題目,你還能脫口而出,告訴我答案是什么嗎?
open class E {}open class E1: E() {}open class A {open fun E.f() {println("E.f in A")}open fun E1.f() {println("E1.f in A")}fun call(e: E) {e.f()} }class A1: A() {override fun E.f() {println("E.f in A1")}override fun E1.f() {println("E1.f in A1")} }fun main(args: Array<String>) {// a)A().call(E())// b)A1().call(E())// c)A().call(E())// d)A().call(E1()) }問題:請告訴a,b,c,d位置代碼執行的輸出結果是什么?
對于這個問題,恐怕你在紙上寫寫畫畫半天也不一定能給出正確答案吧。關于這個問題,其實我之前的一篇文章 [ [Kotlin] Lambda and Extension](https://www.jianshu.com/p/d7a... 中有提到過。可是,我認為這篇文章關于這部分的解釋不夠清晰,有必要再詳細闡述一次。
Ok,let's started。
為了解決這個問題,官方提出了兩個新的概念:dispatch receiver和extension receiver。
- dispatch receiver:中文翻譯為分發接收者。所謂的分發接收者,就是聲明這個擴展方法所在的類。即:在哪個類中聲明,那個類就是你的分發接收者。
- extension receiver:中文翻譯為擴展接收者。所謂的擴展接收者,就是你實際擴展的那個類。舉個例子:你針對Int類擴展了一個方法add,這個add方法的擴展接收者就是Int類實例。
為了簡化,這里我們將dispatch receiver簡稱為DR,將extension receiver簡稱為ER。
還記得多態的概念嗎?多態是一種運行時概念,即對象的類型要等到運行時才能最終確定。因此,一些語言中也將多態叫做類型延遲加載。解決上面這個問題我們需要關注就是擴展函數是否會產生多態行為。
這里我們將產生多態行為的技術叫做動態解析,與之相反的行為稱之為靜態解析。
為了解決上面的問題,你需要記住下面這個規則:
- DR類型是動態解析的
- 與之相反,ER類型是靜態解析的
先看上面例子的a、b部分,很顯然:
- a代碼中f函數的DR是類A,ER是類E
- b代碼中f函數的DR是類A1,ER是類E
參照上面的規則,由于DR類型是動態解析的。在A1類中我們重寫了E的擴展函數f,運行時最終會執行A1類中擴展的f方法。a部分很明顯會輸出A類中擴展的f方法。因此,最終的輸出結果如下:
E.f in A E.f in A1繼續看c、d部分,c、d部分的DR都是A,而對于ER,c、d分別是E、E1。參照上面的規則,ER是靜態解析的。在call方法聲明的地方,我們傳入的對象類型是E,這就決定了無論擴展方法是來自E還是其子類,將始終執行E類的擴展方法。因此,c、d部分將輸出同樣的結果:
E.f in A E.f in A由此可見,如果你牢記上述兩條規則,解決問題將變得非常容易。為了加強你的記憶,我用一個表格總結上面的知識點:
| 概念 | 擴展方法聲明所在的類 | 聲明擴展方法的類 |
| 解析方式 | 動態解析 | 靜態解析 |
PS:由于新版本Kotlin中針對擴展函數也加入了override關鍵字,這非常有助于DR和ER的理解。如果你在使用Kotlin,強烈建議你更新到最新版本。
不太一樣的this指針
在Java語言中,如果你在內部類中需要外部類的引用可以將this寫在類名后面。可是,試試看Kotlin,果斷不行。
為了獲得外部類的引用,Kotlin語言引入了@符號。舉個例子:
class Outer {inner class Inner {fun f() {println(this@Outer)}} }可以看到,為了獲取外部類的引用,只需要在@后面接外部類的名稱即可。
如果對應一個擴展函數,this引用指向是什么呢?先說答案,擴展函數中的this指針指向ER,即實際擴展的那個類對象。
fun Outer.foo() {println(this) }這里的this指向foo函數的接收者Outer類實例。
this指針還有一種場景是用在lambda表達式中,這是一種比較特殊的使用場景。lambda表達式本身沒有任何接收者,如果是在全局聲明一個lambda表達式,將不能使用this指針。而如果是在某個類或者擴展方法中使用this指針,將指向實際所在類或者擴展方法的接收者。
如果你習慣了Kotlin語言的這種表達方式,this指針的指向就不再是一個問題了。在你習慣這種用法之前,我用一個表格簡單總結一下this指針的用法:
| 類中 | 默認指向當前類實例,使用@操作符指向具體外部類實例 |
| 擴展函數 | 默認指向擴展函數的接收者 |
| lambda表達式 | 默認指向實際所在類實例或所在擴展函數的接收者 |
總結
關于擴展,大多數情況下,你不會遇到文章開頭那種復雜的情況。如果遇到了這種情況,只要清楚地區分DR和ER,并牢記DR和ER的解析方式,就能輕松應對了。對于this指針,與Java語言不一樣的地方是,為了引用具體類的實例,Kotlin語言使用@符號。個人認為,這種表述方式更自然。如果遇到某些比較復雜的情況,只需要弄清楚接收者,問題就引刃而解了。
歡迎加入Kotlin交流群
如果你也喜歡Kotlin語言,歡迎加入我的Kotlin交流群: 329673958 ,一起來參與Kotlin語言的推廣工作。
編程,我們是認真的!
關注歐陽鋒工作室公眾號,你想要的都在這里:
總結
以上是生活随笔為你收集整理的Kotlin难点解析:extension和this指针的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot微信点餐——实战开
- 下一篇: 运行期优化