Kotlin 擴(kuò)展函數(shù)和擴(kuò)展屬性的使用方法
Kotlin 能夠擴(kuò)展一個(gè)類的新功能而無需繼承該類或者使用像裝飾者這樣的設(shè)計(jì)模式。 這通過叫做 擴(kuò)展 的特殊聲明完成。 例如,你可以為一個(gè)你不能修改的、來自第三方庫中的類編寫一個(gè)新的函數(shù)。 這個(gè)新增的函數(shù)就像那個(gè)原始類本來就有的函數(shù)一樣,可以用普通的方法調(diào)用。 這種機(jī)制稱為 擴(kuò)展函數(shù) 。此外,也有 擴(kuò)展屬性 , 允許你為一個(gè)已經(jīng)存在的類添加新的屬性。
前言
作為安卓開發(fā),我們常常碰到這樣的場(chǎng)景,需要把以dp為單位的值轉(zhuǎn)化為以px為單位。這時(shí)候我們常會(huì)寫一個(gè)Utils類,比如說
public class Utils { public static float dp2px(int dpValue) { return (0.5f + dpValue * Resources.getSystem().getDisplayMetrics().density); } }
在代碼中直接調(diào)用 Utils.dp2px(100) 來使用,
val dp2px = Utils.dp2px(100)
如果用kotlin擴(kuò)展函數(shù)的方式來實(shí)現(xiàn),會(huì)是怎么調(diào)用呢?
val dp2px = 100.dp2px()
是不是很驚訝,100作為一個(gè)Int,竟然直接調(diào)用了一個(gè)dp2px方法,如果你去源碼里找找,其實(shí)是沒有個(gè)方法的。我們沒有動(dòng)源碼,而是使用拓展函數(shù)的方式為Int增加了一個(gè)方法。
fun Int.dp2px(): Float { return (0.5f + this * Resources.getSystem().displayMetrics.density) }
擴(kuò)展函數(shù)
我們?cè)賮砼e個(gè)🌰,有一個(gè)Person類如下
class Person(val name: String) { fun eat() { Log.i(name, "I'm going to eat") } fun sleep() { Log.i(name, "I'm going to sleep") } }
它有兩個(gè)方法,一個(gè)是 eat 、一個(gè)是 sleep,調(diào)用的話就分別打印相應(yīng)的Log。我們現(xiàn)在不想動(dòng)Person類,但是又想給他增加一個(gè)新的方法,怎么做呢。我們可以新建一個(gè)文件 PersonExtensions.kt,再通過一下代碼實(shí)現(xiàn),就可以為 Person類新增一個(gè) drink 方法啦。
fun Person.drink() { Log.i("Person", "${this.name}: I'm going to drink") }
聲明一個(gè)擴(kuò)展函數(shù),我們需要用一個(gè) 接收者類型 也就是被擴(kuò)展的類型來作為他的前綴。上面我們就是以 Person 作為一個(gè)擴(kuò)展函數(shù)的接收類型,為其拓展來 drink 方法。我們?cè)谄浞椒ㄖ姓{(diào)用了 this ,這個(gè) this 指的就是調(diào)用這個(gè)拓展方法的當(dāng)前 Person 對(duì)象。
擴(kuò)展函數(shù)調(diào)用的話也和普通的方法相同。但是你會(huì)發(fā)現(xiàn)IDE顯示的方法顏色有點(diǎn)不一樣。
由此也可以看出普通的方法和我們的拓展函數(shù)是不同的。下面我們來看看擴(kuò)展函數(shù)的實(shí)際實(shí)現(xiàn)。
在 Android Studio 中,我們可以查看 kotlin 文件的字節(jié)碼,然后再 Decompile 為 Java 代碼。上面我們?yōu)?Person 擴(kuò)展函數(shù)轉(zhuǎn)為Java代碼后如下。
@Metadata( mv = {1, 1, 15}, bv = {1, 0, 3}, k = 2, d1 = {"\u0000\f\n\u0000\n\u0002\u0010\u0002\n\u0002\u0018\u0002\n\u0000\u001a\n\u0010\u0000\u001a\u00020\u0001*\u00020\u0002¨\u0006\u0003"}, d2 = {"cook", "", "Lcom/chaochaowu/kotlinextension/Person;", "app_debug"} ) public final class PersonExtensionsKt { public static final void cook(@NotNull Person $this$cook) { Intrinsics.checkParameterIsNotNull($this$cook, "$this$cook"); Log.i("Person", $this$cook.getName() + ": I'm going to cook"); } }
妹想到啊,它原來是一個(gè) static final 聲明的靜態(tài)方法,它的入?yún)⑹且粋€(gè) Person 類型,也就是我們之前的接收類型。那在Java代碼中能不呢調(diào)用呢?
PersonExtensionsKt.cook(new Person("Bob"));
竟然也沒有報(bào)錯(cuò)!由此可見,所謂擴(kuò)展函數(shù)并不是真正的在類中增加了一個(gè)方法,而是通過外部文件的靜態(tài)方法來實(shí)現(xiàn),其實(shí)就是和Utils類一個(gè)道理。
因?yàn)閷⒁粋€(gè) Person 作為入?yún)魅肓朔椒ㄖ?,所以我們也就可以在方法?nèi)對(duì)這個(gè) Person 對(duì)象進(jìn)行操作,這也就是在擴(kuò)展方法中我們可以使用 this 來訪問 Person 屬性的原因。
再來看一個(gè)特殊的例子。
val s: String? = null s.isNullOrEmpty()
上面的代碼中,s的值為null,我們用null去調(diào)用了一個(gè)方法,這會(huì)不會(huì)報(bào)錯(cuò)呢?按照以前的經(jīng)驗(yàn),一個(gè)null去調(diào)用一個(gè)方法,必然會(huì)報(bào)空指針的異常,但是上面的代碼卻是不會(huì)崩的。為什么哩?
其實(shí) isNullOrEmpty 是 CharSequence? 的一個(gè)擴(kuò)展方法,我們可以看一下它的源碼。
@kotlin.internal.InlineOnly public inline fun CharSequence?.isNullOrEmpty(): Boolean { contract { returns(false) implies (this@isNullOrEmpty != null) } return this == null || this.length == 0 }
contract這個(gè)契約方法這邊我們不需要注意,不影響。主要是看 return this == null || this.length == 0 這句話。它先是判斷了 this 是否為空,然后再判斷this 的長(zhǎng)度。根據(jù)我們上面講的擴(kuò)展函數(shù)的本質(zhì),我們可以很好的理解,為什么null可以調(diào)用這個(gè)方法的原因。因?yàn)樯厦娴拇a轉(zhuǎn)為 Java 代碼后是這樣子的。
public static final boolean isNullOrEmpty(@Nullable CharSequence $this$isNullOrEmpty) { int $i$f$isNullOrEmpty = 0; return $this$isNullOrEmpty == null || $this$isNullOrEmpty.length() == 0; }
我們?cè)谟胣ull調(diào)用這個(gè)擴(kuò)展方法時(shí),其實(shí)是將null作為一個(gè)參數(shù)傳入這個(gè)方法中,先判斷參數(shù)是否為null,再進(jìn)行下一步判斷,這當(dāng)然不會(huì)崩潰。
擴(kuò)展不能真正的修改他們所擴(kuò)展的類。通過定義一個(gè)擴(kuò)展,你并沒有在一個(gè)類中插入新成員, 僅僅是可以通過該類型的變量用點(diǎn)表達(dá)式去調(diào)用這個(gè)新函數(shù),并將自身作為參數(shù)傳入。
擴(kuò)展屬性
擴(kuò)展屬性和擴(kuò)展函數(shù)類似,再舉上面Person 的例子,我們對(duì) Person 類稍作修改,為其增加 birthdayYear 字段,表示其出生的年份。
class Person(val name: String, val birthdayYear: Int) { fun eat() { Log.i(name, "I'm going to eat") } fun sleep() { Log.i(name, "I'm going to sleep") } }
我們現(xiàn)在要為 Person 增加年齡 age 的屬性,但是不改 Person 類,怎么實(shí)現(xiàn)呢。和擴(kuò)展函數(shù)一樣,在其他文件中聲明如下。
const val currentYear = 2019 val Person.age: Int get() = currentYear - this.birthdayYear
我們通過當(dāng)前年份減去生日年份計(jì)算出 Person 的年齡??梢钥吹?,age 是一個(gè)屬性,而不是方法。這樣我們就為 Person 增加了一個(gè)擴(kuò)展屬性??梢钥纯此D(zhuǎn)化為 Java 代碼后的樣子,和擴(kuò)展函數(shù)沒啥區(qū)別。
@Metadata( mv = {1, 1, 15}, bv = {1, 0, 3}, k = 2, d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\b\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\"\u000e\u0010\u0000\u001a\u00020\u0001X\u0086T¢\u0006\u0002\n\u0000\"\u0015\u0010\u0002\u001a\u00020\u0001*\u00020\u00038F¢\u0006\u0006\u001a\u0004\b\u0004\u0010\u0005¨\u0006\u0006"}, d2 = {"currentYear", "", "age", "Lcom/chaochaowu/kotlinextension/Person;", "getAge", "(Lcom/chaochaowu/kotlinextension/Person;)I", "app_debug"} ) public final class PersonExtensionsKt { public static final int currentYear = 2019; public static final int getAge(@NotNull Person $this$age) { Intrinsics.checkParameterIsNotNull($this$age, "$this$age"); return 2019 - $this$age.getBirthdayYear(); } }
上面我們聲明的是一個(gè) val,當(dāng)然也可以聲明一個(gè) var,不過 var 的話需要同時(shí)定義其 get 和 set 方法。
由于擴(kuò)展沒有實(shí)際的將成員插入類中,因此對(duì)擴(kuò)展屬性來說幕后字段是無效的。這就是為什么擴(kuò)展屬性不能有初始化器。他們的行為只能由顯式提供的 getters/setters 定義。
總結(jié)
在 Java 中,我們要擴(kuò)展一個(gè)類時(shí),常常是繼承該類或者用裝飾者模式類似的設(shè)計(jì)模式來實(shí)現(xiàn),Kotlin 擴(kuò)展函數(shù)和擴(kuò)展屬性為這種需求提供了一種新思路,并且也可以作為 Utils 類的另外一種選擇,值得一試。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android帶數(shù)字或紅點(diǎn)的底部導(dǎo)航攔和聯(lián)網(wǎng)等待加載動(dòng)畫示例
這篇文章主要介紹了Android帶數(shù)字或紅點(diǎn)的底部導(dǎo)航攔和聯(lián)網(wǎng)等待加載動(dòng)畫示例,具有一定的參考價(jià)值,有興趣的同學(xué)可以了解一下。2017-03-03Android自定義控件實(shí)現(xiàn)可左右滑動(dòng)的導(dǎo)航條
這篇文章主要介紹了Android自定義控件實(shí)現(xiàn)可左右滑動(dòng)的導(dǎo)航條,能響應(yīng)快速滑動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07Android使用Photoview實(shí)現(xiàn)圖片左右滑動(dòng)及縮放功能
這篇文章主要為大家詳細(xì)介紹了Android使用Photoview實(shí)現(xiàn)圖片左右滑動(dòng)及縮放功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01Android基于騰訊云實(shí)時(shí)音視頻仿微信視頻通話最小化懸浮
這篇文章主要為大家詳細(xì)介紹了Android基于騰訊云實(shí)時(shí)音視頻實(shí)現(xiàn)類似微信視頻通話最小化懸浮,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11Android?NDK入門初識(shí)(組件結(jié)構(gòu)開發(fā)流程)
這篇文章主要為大家介紹了Android?NDK入門之初識(shí)組件結(jié)構(gòu)開發(fā)流程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08Android自定義圓形View實(shí)現(xiàn)小球跟隨手指移動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了Android自定義圓形View實(shí)現(xiàn)小球跟隨手指移動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03Core Animation一些Demo總結(jié) (動(dòng)態(tài)切換圖片、大轉(zhuǎn)盤、圖片折疊、進(jìn)度條等動(dòng)畫效果)
這篇文章主要介紹了Core Animation一些Demo總結(jié) (動(dòng)態(tài)切換圖片、大轉(zhuǎn)盤、圖片折疊、進(jìn)度條等動(dòng)畫效果)的相關(guān)資料,需要的朋友可以參考下2016-02-02