学习Kotlin(六)扩展与委托
?
推薦閱讀:
學習Kotlin(一)為什么使用Kotlin
學習Kotlin(二)基本語法
學習Kotlin(三)類和接口
學習Kotlin(四)對象與泛型
學習Kotlin(五)函數與Lambda表達式
學習Kotlin(六)擴展與委托
學習Kotlin(七)反射和注解
學習Kotlin(八)其他技術
Kotlin學習資料總匯
?
目錄
1.擴展
- 1.1 擴展函數
- 1.2 擴展屬性
- 1.3 擴展伴生對象
- 1.4 擴展的作用域
2.委托
- 2.1 類委托
- 2.2 委托屬性
- 2.3 標準委托
1.擴展
在Kotlin中,允許對類進行擴展,不需要繼承該類或使用像裝飾者這樣的任何類型的設計模式,通過一種特殊形式的聲明,來實現具體實現某一具體功能。擴展函數是靜態解析的,并未對原類增添函數或者屬性,對類本身沒有影響。
1.1擴展函數
聲明一個擴展函數,我們需要用一個接收者類型也就是被擴展的類型來作為他的前綴。 下面代碼為Kotlin原生集合類 MutableList 添加一個 swap 函數:
fun MutableList<Int>.swap(index1: Int, index2: Int) {val tmp = this[index1] // “this”對應該列表this[index1] = this[index2]this[index2] = tmp }上面代碼用MutableList作為接受者類型,對其進行擴展,為其添加一個 swap 函數,當我們調用 swap 函數時,可以這樣:
val mutableList = mutableListOf(1, 2, 3) mutableList.swap(1, 2) //調用擴展函數swap() println(mutableList)運行代碼,得到結果
- 內部成員函數名與擴展函數名相同
如果擴展函數與內部成員函數沖突,如下所示:
運行代碼,得到結果:
由上面例子可得,如果擴展函數的函數名跟內部成員函數的函數名沖突,會優先調用內部成員函數。
?
- 可空接收者
可以為可空的接收者類型定義擴展,如下所示:
運行代碼,得到結果:
上面代碼中,我們對可空的接受者定義擴展,檢測調用者是否為null,如果為null,返回"null",如果不為null,返回Any.toString()
?
1.2擴展屬性
擴展屬性是對屬性的擴展,如下所示:
class User {//必須聲明為public(Kotlin默認是public)//否則擴展屬性無法訪問該變量var mValue = 0 }//擴展屬性 var User.value: Intget() = mValueset(value) {mValue = value}//調用擴展函數 var user = User() user.value = 2 println(user.value)運行代碼,得到結果:
上面代碼中對 mValue 進行了屬性擴展,抵用了擴展屬性實現了 setter 方法,對 mValue 進行賦值,再通過擴展屬性實現了 getter 方法,獲取到 mValue 的值。
?
1.3擴展伴生對象
除了擴展函數和擴展屬性外,還可以對伴生對象進行擴展,代碼如下:
class User {companion object {} }//擴展伴生對象 fun User.Companion.foo() {println("伴生對象擴展") }//調用 User.foo()運行代碼,得到結果
1.4擴展的作用域
- 在不同包里進行擴展
上面的代碼都是在同一個包里進行擴展,如果在不同包里要進行擴展,就要用import來導入資源了,如下所示:
在其他包中調用
package com.demo.czh.activitydemoimport com.demo.czh.otherpackage.OtherUser import com.demo.czh.otherpackage.printUser().print() OtherUser().print()運行代碼,得到結果:
由上面例子可得,如果要在不用的包里進行擴展,要在調用處 import 擴展的資源。
?
- 擴展聲明為成員
在一個類內部你可以為另一個類聲明擴展,如下所示:
運行代碼,得到結果:
擴展聲明所在的類的實例稱為 分發接收者,擴展方法調用所在的接收者類型的實例稱為 擴展接收者 。對于分發接收者和擴展接收者的成員名字沖突的情況,擴展接收者優先。如果要引用分發接收者的成員,可以這樣寫:
?
//User類不變 class User {fun printUser(){println("User")} }//User2 class User2 {fun printUser() {println("User2")}fun User.print() {printUser()//表示調用User2的printUser()函數this@User2.printUser()}fun getUser(user: User) {//調用擴展方法user.print()} }運行代碼,得到結果:
上面 User.print() 這個擴展函數中,用到了 限定的 this 語法來調用 User2 的printUser() 函數。
?
- 擴展成員的繼承
聲明為成員的擴展可以聲明為 open 并在子類中覆蓋。這意味著這些函數的分發對于分發接收者類型是虛擬的,但對于擴展接收者類型是靜態的。
運行代碼,得到結果:
2.委托
在Kotlin中,如果有多個地方用到了相同的代碼,可以用委托來處理。
2.1類委托
委托模式是實現繼承一個很好的的替代方式,Kotlin支持委托模式,不用為了實現委托模式而編寫樣板代碼。舉個例子:
//定義一個接口 Base interface Base {fun print() }//定義一個 ImplBase 實現接口 Base class ImplBase(val i: Int) : Base {override fun print() {println(i)} }//定義一個 Drived 類實現接口 Base class Drived(b: Base) : Base {//這里需要 override 接口 Base 里的方法override fun print() {} }//如果使用委托模式的話,可以把 Base 里的方法委托給 Drived class Drived(b: Base) : Base by b//調用 print() 方法 var b = ImplBase(10) Drived(b).print()//運行代碼,打印結果為 10從上面代碼可以看出,Derived 類通過使用 by 關鍵字將 Base 接口的 print 方法委托給對象 b ,如果不進行委托的話,則要 override Base 接口的 print 方法。如果出現委托后仍然 override 的情況,編譯器會使用你的 override 實現取代委托對象中的實現,如下所示:
//委托后仍然 override class Drived(b: Base) : Base by b {override fun print() {println("abc")} }//調用 print() 方法 var b = ImplBase(10) Drived(b).print()//運行代碼,打印結果為 abc2.2 委托屬性
在實際應用中,有很多類的屬性都擁有 getter 和 setter 函數,這些函數大部分都是相同的。Kotlin允許委托屬性,把所有相同的 getter 和 setter 函數放到同一個委托類中,這樣能大大減少冗余代碼。舉個例子:
class User1 {var userName: String = ""get() = fieldset(value) {field = value} } class User2 {var userName: String = ""get() = fieldset(value) {field = value} }User1和User2都有相同的 getter 和 setter 函數,把它們放到委托類中,如下:
//定義一個委托類Delegate class Delegate {var userName = ""operator fun getValue(thisRef: Any?, property: KProperty<*>): String {println("getValue 類名:$thisRef, 屬性名:${property.name}")return userName}operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {println("setValue 類名:$thisRef, 屬性名:${property.name},值:$value")userName = value} }//將 userName 委托給 Delegate class User1 {var userName: String by Delegate() } class User2 {var userName: String by Delegate() }//調用 getter 和 setter 函數 var user1 = User1() user1.userName = "user1" println(user1.userName) var user2 = User2() user2.userName = "user2" println(user2.userName)運行代碼,得到結果:
可以看到,User1 和 User2 都將 userName 委托給 Delegate ,在 Delegate 內完成 getter/setter 函數,去除了相同的代碼。
2.3 標準委托
Kotlin標準庫中提供了一些有用的委托函數:
- 延遲委托
- 可觀察屬性委托
- Map委托
延遲委托
lazy()是接受一個 lambda 表達式作為參數,并返回一個?Lazy <T>?實例的函數,返回的實例作為一個委托,第一次調用?get()?會執行已傳遞給?lazy()?的 lambda 表達式并記錄結果, 之后再調用?get()?返回記錄的結果。
val lazyValue: String by lazy {println("computed!")"Hello" }//調用兩次 println(lazyValue) println(lazyValue)運行代碼,得到結果:
默認情況下,對于 lazy 屬性的求值是同步鎖的(synchronized):該值只在一個線程中計算,并且所有線程會看到相同的值。如果初始化委托的同步鎖不是必需的,這樣多個線程可以同時執行,那么將 LazyThreadSafetyMode.PUBLICATION 作為參數傳遞給 lazy() 函數。如下所示:
?
val lazyValue: String by lazy(LazyThreadSafetyMode.PUBLICATION ) {"Hello" }而如果你確定初始化將總是發生在單個線程,那么你可以使用 LazyThreadSafetyMode.NONE 模式, 它不會有任何線程安全的保證和相關的開銷,如下所示:
val lazyValue: String by lazy(LazyThreadSafetyMode.NONE) {"Hello" }可觀察屬性委托
實現可觀察屬性委托的函數是Delegates.observable(),當我們使用該委托函數時,可以觀察屬性的變化,如下所示:
var name: String by Delegates.observable("Czh") { property, oldValue, newValue ->println("屬性名:$property 舊值:$oldValue 新值:$newValue") }//修改name的值 name = "abc" name = "hello"運行代碼,得到結果:
Delegates.observable()接收兩個參數,第一個是初始值,第二個是修改時處理程序(handler)。 每當我們給屬性賦值時會調用該處理程序,他有三個參數,第一個是被賦值的屬性,第二個是舊值,第三個是新值。如果想攔截屬性的賦值操作,并且否決他的賦值操作,可以用vetoable()取代?observable(),傳遞給vetoable()的修改時處理程序會返回一個boolean類型,如果返回true,允許賦值,返回false則反之。如下所示:
?
var name: String by Delegates.vetoable("Czh") { property, oldValue, newValue ->if (newValue.equals("abc")) {println("屬性名:$property 舊值:$oldValue 新值:$newValue")true} else {println("不能修改為除了abc以外的值")false} }//修改name的值 name = "abc" name = "hello"運行代碼,得到結果:
Map委托
Map委托是指用Map實例自身作為委托來實現委托屬性,通常用于解析 JSON ,如下所示:
//新建User類,主構函數要求傳入一個Map class User(val map: Map<String, Any>) {//聲明一個 String 委托給 mapval name: String by map//因為 Map 為只讀,所以只能用 val 聲明val age: Int by map }var map = mapOf("name" to "Czh", "age" to 22) var user = User(map) println("${user.name} ${user.age}") //打印結果為 Czh 22因為Map只有getValue方法而沒有setValue方法,所以不能通過User對象設置值,這時可以把User的主構函數改為傳入一個MutableMap,并把屬性委托給MutableMap,如下所示:
class User(val map: MutableMap<String, Any>) {//因為MutableMap為讀寫,可以用var聲明var name: String by mapvar age: Int by map }var map = mutableMapOf("name" to "Czh", "age" to 22) var user = User(map) user.name = "James Harden" user.age = 28 println("${user.name} ${user.age}") //打印結果為 James Harden 28總結
本篇文章簡述了Kotlin中擴展和委托的使用方法。擴展和委托都是Kotlin自身支持并非常好用的,擴展能使代碼更靈活,委托能實現代碼重用。運用好他們能很好地加快編寫代碼的速度。
原文鏈接:https://juejin.im/post/5a9648466fb9a0633b213d73
總結
以上是生活随笔為你收集整理的学习Kotlin(六)扩展与委托的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习Kotlin(五)函数与Lambda
- 下一篇: 学习Kotlin(七)反射和注解