Kotlin 擴展函數(shù)及原理

為什么需要擴展函數(shù)?

在很多公司一些比較穩(wěn)定良好的庫都是 Java 開發(fā)的明吩,也完全沒必要去用 Kotlin 語言重寫蓝晒。但是想要擴展庫的接口和功能,這時候擴展函數(shù)可能就會派上用場说订。使用 Kotlin 的擴展函數(shù)還有一個好處就是沒有副作用灵临,不會對原有庫代碼或功能產(chǎn)生影響。

先來一個擴展函數(shù)的例子讼载,來實現(xiàn)一個給 Android 中 TextView 組件設置加粗的擴展函數(shù):

// 擴展函數(shù)的定義
fun TextView.isBold() = this.apply { 
    paint.isFakeBoldText = true
}

// 擴展函數(shù)的調(diào)用
activity.find<TextView>(R.id.course_comment_tv_score).isBold()

擴展函數(shù)的語法

Kotlin 的擴展函數(shù)可以毫無副作用給原有庫的類增加屬性和方法轿秧,比如例子中 TextView,我們根本沒有去動 TextView 源碼咨堤,但是卻給它增加一個擴展函數(shù)菇篡。具有那么強大功能,到底它背后原理是什么呢一喘?下面我們來深入學習下它的語法以及語法背后的原理驱还。

擴展函數(shù)的定義

Kotlin 可以為一個不能修改的或來自第三方庫中的類編寫一個新的函數(shù)。 這個新增的函數(shù)就像那個原始類本來就有的函數(shù)一樣凸克,可以用普通的方法調(diào)用议蟆,這種機制的函數(shù)稱為擴展函數(shù)

// 擴展函數(shù)定義
fun TextView.isBold() = this.apply { 
    paint.isFakeBoldText = true
}

擴展函數(shù)的分析

擴展函數(shù)只需要把擴展的類或者接口名稱萎战,放到即將要添加的函數(shù)名前面咐容。這個類或者名稱就叫做接收者類型,類的名稱與函數(shù)之間用 . 調(diào)用連接蚂维。this 指代的就是接收者對象戳粒,它可以訪問擴展的這個類可訪問的函數(shù)或?qū)傩?/strong>路狮。

擴展函數(shù)的背后原理

擴展函數(shù)實際上就是一個對應 Java 中的靜態(tài)函數(shù),這個靜態(tài)函數(shù)參數(shù)為接收者類型的對象蔚约,然后利用這個對象就可以訪問這個類中的成員屬性和方法了览祖,并且最后返回一個這個接收者類型對象本身。這樣在外部感覺和使用類的成員函數(shù)是一樣的:

// 這個類名就是頂層文件名+“Kt”后綴
public final class ExtendsionTextViewKt {
   // 擴展函數(shù) isBold 對應實際上是 Java 中的靜態(tài)函數(shù)炊琉,并且傳入一個接收者類型對象作為參數(shù)
   @NotNull
   public static final TextView isBold(@NotNull TextView $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.getPaint().setFakeBoldText(true); // 設置加粗
      return $receiver; // 最后返回這個接收者對象自身展蒂,以致于我們在Kotlin中完全可以使用 this 替代接收者對象或者直接不寫。
   }
}

擴展函數(shù)的使用場景

擴展函數(shù)的使用場景主要就是毫無副作用給原有庫的類增加函數(shù)苔咪,增強類的行為锰悼。比如 Android 中給所有 View 組件增加常見動畫、給 ImageView 組件增加一個加載網(wǎng)絡圖片函數(shù)团赏、給 SpannableStringBuilder 增加 bold 加粗箕般、italic 斜體、color 顏色等一系列 Span 的設置以及很多需要重復設置模板代碼場景都可以使用擴展函數(shù)舔清。

擴展函數(shù)的使用示例

示例1: Android 中給 ImageView 組件增加一個 loadUrl 的擴展函數(shù),不再需要繁瑣 Glide 的調(diào)用体谒,只需簡單一行 loadUrl 代碼的調(diào)用就能實現(xiàn)加載圖片功能:

// loadUrl擴展函數(shù)接收者類型是ImageView
fun ImageView.loadUrl(requestManager: RequestManager = Glide.with(context)
                      , url: String = ""
                      , urls: List<String> = listOf(url)) {
    // this 指代的是ImageView這個接收者對象實例, 這里this也可以省略
    requestManager.view(this).url(urls).start() 
}

// 使用擴展函數(shù)來加載多張圖片的調(diào)用方式
// imageView直接調(diào)用loadUrl像是給ImageView增加一個網(wǎng)絡圖片加載函數(shù)一樣抒痒,調(diào)用非常簡單。
course_cover_iv1.loadUrl("https://www.imooc.com/test1.png") 
course_cover_iv2.loadUrl("https://www.imooc.com/test2.png")
course_cover_iv3.loadUrl("https://www.imooc.com/test3.png")

// 不使用擴展函數(shù)來加載多張圖片的調(diào)用方式
Glide.with(context)
     .view(course_cover_iv1)
     .url("https://www.imooc.com/test1.png")
     .start()

Glide.with(context)
     .view(course_cover_iv2)
     .url("https://www.imooc.com/test2.png")
     .start()

Glide.with(context)
     .view(course_cover_iv3)
     .url("https://www.imooc.com/test3.png")
     .start()     

示例2: Android 中給 View 組件增加一個 animAlpha 透明動畫的擴展函數(shù)傀广,不需要每次都去重新寫屬性動畫的配置彩届,只需要簡單一行代碼調(diào)用 View 中的方法即可。像是給原本就不具備透明動畫 View 組件無縫地提供一個動畫的功能:

// animAlpha 擴展函數(shù)接收者類型是 View,也就是 View 的子類都可以直接使用 animAlpha 函數(shù)
fun View.animAlpha(
       duration: Long = 600,
       alpha: Float = 0f, 
       delay: Long = 0, 
       animatorEnd: ((Animator?) -> Unit)? = null
 ) {
        this.animate() // this 指代的是 View 這個接收者對象實例, 這里 this 也可以省略
            .setDuration(duration)
            .setInterpolator(AccelerateDecelerateInterpolator())
            .setStartDelay(delay)
            .alpha(alpha)
            .setListener(object : Animator.AnimatorListener {
                override fun onAnimationRepeat(animation: Animator?) {

                }

                override fun onAnimationEnd(animation: Animator?) {
                    animatorEnd?.invoke(animation)
                }

                override fun onAnimationCancel(animation: Animator?) {

                }

                override fun onAnimationStart(animation: Animator?) {

                }
            })
            .start()
}

// animAlphaVisible 擴展函數(shù)贮聂,設置 View 組件的顯示和消失透明度動畫坯墨,通過i sVisible 控制
fun View.animAlphaVisible(
       isVisible: Boolean, 
       duration: Long = 600, 
       animatorEnd: ((Animator?) -> Unit)? = null
) {
    // 擴展函數(shù)內(nèi)部再調(diào)用擴展函數(shù) animAlpha
    animAlpha(duration, alpha = if (isVisible) 1f else 0f, animatorEnd = animatorEnd)
}

// 擴展函數(shù) animAlpha 調(diào)用
mEditText.animAlpha(alpha = 0.2f) // EditText 組件通過最開始透明度變化到 alpha 為 0.2f

// 擴展函數(shù) animAlphaVisible 調(diào)用
mIv.animAlphaVisible(isVisible = true) // ImageView 組件設置透明度顯示的動畫
mTv.animAlphaVisible(isVisible = false) // TextView 組件設置透明度消失的動畫

擴展函數(shù)需要注意的點

  • 擴展函數(shù)不能像成員函數(shù)那樣被子類重寫捣染;

  • 擴展函數(shù)不能訪問原始類中私有成員屬性或成員函數(shù)停巷;

  • 擴展函數(shù)的本質(zhì)通過傳入對象實例榕栏,委托對象來訪問成員函數(shù)蕾各,所以它的訪問權限和對象訪問權限一致。

擴展函數(shù)與成員函數(shù)的區(qū)別

擴展函數(shù)在外部調(diào)用方式來看和類的成員函數(shù)是一致式曲,但是兩者卻有著本質(zhì)的區(qū)別。

  • 擴展函數(shù)和成員函數(shù)使用方式類似兰伤,可以直接訪問被擴展類的方法和屬性钧排。(原理: 傳入了一個擴展類的對象,內(nèi)部實際上是用實例對象去訪問擴展類的方法和屬性)恨溜;

  • 擴展函數(shù)不能打破擴展類的封裝性,不能像成員函數(shù)一樣直接訪問內(nèi)部私有函數(shù)和屬性判族。(原理: 原理很簡單项戴,擴展函數(shù)訪問實際是類的對象訪問,由于類的對象實例不能訪問內(nèi)部私有函數(shù)和屬性沃缘,自然擴展函數(shù)也就不能訪問內(nèi)部私有函數(shù)和屬性了)则吟;

  • 擴展函數(shù)實際上是一個靜態(tài)函數(shù)是處于類的外部,而成員函數(shù)則是類的內(nèi)部函數(shù)氓仲;

  • 父類成員函數(shù)可以被子類重寫,而擴展函數(shù)則不行晰洒。

擴展函數(shù)使用經(jīng)驗

雖然擴展函數(shù)功能十分強大啥箭,但是記住千萬不要濫用擴展函數(shù),并不是所有場景定義都合適定義成擴展函數(shù)砌滞。定義成擴展函數(shù)需要抓住以下三個點:

  • 第一,這個函數(shù)需要被很多地方復用贝润,定義成擴展函數(shù)減少很多重復模板代碼;

  • 第二华畏,這個函數(shù)具有擴展的原始類尊蚁,如果沒有那么就不適合定義成擴展函數(shù),它經(jīng)常很容易會和頂層函數(shù)使用場景弄混枝誊;

  • 第三,如果需要擴展一個原始類的行為時候用擴展函數(shù)绝骚,如果是擴展一個原始類的狀態(tài)的時候建議使用擴展屬性祠够。

總結

擴展函數(shù)的本質(zhì)是一個對應 Java 中的靜態(tài)函數(shù),這個靜態(tài)函數(shù)參數(shù)為接收者類型的對象止剖,然后利用這個對象就可以訪問這個類中的成員屬性和方法了落君,并且最后返回一個這個接收者類型對象本身。這樣在外部感覺和使用類的成員函數(shù)是一樣的皮获。另外纹冤,擴展函數(shù)與成員函數(shù)的相同點和不同點也是擴展函數(shù)中的重點。

參考:
Kotlin 擴展函數(shù)

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末雁歌,一起剝皮案震驚了整個濱河市知残,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖印蔗,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丑勤,死亡現(xiàn)場離奇詭異法竞,居然都是意外死亡强挫,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門呆细,熙熙樓的掌柜王于貴愁眉苦臉地迎上來八匠,“玉大人,你說我怎么就攤上這事梨树÷账模” “怎么了?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵淑履,是天一觀的道長藻雪。 經(jīng)常有香客問我,道長阔涉,這世上最難降的妖魔是什么瑰排? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮崇渗,結果婚禮上,老公的妹妹穿的比我還像新娘葫掉。我一直安慰自己,他們只是感情好跟狱,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布驶臊。 她就那樣靜靜地躺著,像睡著了一般扛门。 火紅的嫁衣襯著肌膚如雪纵寝。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天葬凳,我揣著相機與錄音闹啦,去河邊找鬼。 笑死荐健,一個胖子當著我的面吹牛琳袄,可吹牛的內(nèi)容都是我干的窖逗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼佑附,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了仗考?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤顿膨,失蹤者是張志新(化名)和其女友劉穎叽赊,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體囊咏,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡匆笤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年谱邪,在試婚紗的時候發(fā)現(xiàn)自己被綠了庶诡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片末誓。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡喇澡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晴玖,到底是詐尸還是另有隱情,我是刑警寧澤让簿,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布秀睛,位于F島的核電站,受9級特大地震影響蹂安,放射性物質(zhì)發(fā)生泄漏椭迎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一田盈、第九天 我趴在偏房一處隱蔽的房頂上張望畜号。 院中可真熱鬧缠黍,春花似錦弄兜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惋砂。三九已至绳锅,卻和暖如春鳞芙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驯嘱。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工鞠评, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留剃幌,地道東北人锥忿。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓怠肋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钉答。 傳聞我的和親對象是個殘疾皇子数尿,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

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