擴展與委託html
在Kotlin中,容許對類進行擴展,不須要繼承該類或使用像裝飾者這樣的任何類型的設計模式,經過一種特殊形式的聲明,來實現具體實現某一具體功能。擴展函數是靜態解析的,並未對原類增添函數或者屬性,對類自己沒有影響。設計模式
聲明一個擴展函數,咱們須要用一個接收者類型也就是被擴展的類型來做爲他的前綴。 下面代碼爲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 函數時,能夠這樣:bash
val mutableList = mutableListOf(1, 2, 3)
mutableList.swap(1, 2) //調用擴展函數swap()
println(mutableList)
複製代碼
運行代碼,獲得結果微信
class User {
fun print() {
println("內部成員函數")
}
}
//擴展函數
fun User.print() {
println("擴展函數")
}
//調用
User().print()
複製代碼
運行代碼,獲得結果:ide
由上面例子可得,若是擴展函數的函數名跟內部成員函數的函數名衝突,會優先調用內部成員函數。//擴展函數
fun Any?.toString(): String {
if (this == null) return "null"
return toString()
}
//調用
var a = null
a.toString()
println(a)
var b = "not null"
b.toString()
println(b)
複製代碼
運行代碼,獲得結果:函數
上面代碼中,咱們對可空的接受者定義擴展,檢測調用者是否爲null,若是爲null,返回"null",若是不爲null,返回Any.toString()擴展屬性是對屬性的擴展,以下所示:post
class User {
//必須聲明爲public(Kotlin默認是public)
//不然擴展屬性沒法訪問該變量
var mValue = 0
}
//擴展屬性
var User.value: Int
get() = mValue
set(value) {
mValue = value
}
//調用擴展函數
var user = User()
user.value = 2
println(user.value)
複製代碼
運行代碼,獲得結果:學習
上面代碼中對 mValue 進行了屬性擴展,抵用了擴展屬性實現了 setter 方法,對 mValue 進行賦值,再經過擴展屬性實現了 getter 方法,獲取到 mValue 的值。除了擴展函數和擴展屬性外,還能夠對伴生對象進行擴展,代碼以下:ui
class User {
companion object {
}
}
//擴展伴生對象
fun User.Companion.foo() {
println("伴生對象擴展")
}
//調用
User.foo()
複製代碼
運行代碼,獲得結果
package com.demo.czh.otherpackage
class OtherUser {
}
fun OtherUser.print() {
println("其餘包的")
}
複製代碼
在其餘包中調用
package com.demo.czh.activitydemo
import com.demo.czh.otherpackage.OtherUser
import com.demo.czh.otherpackage.print
User().print()
OtherUser().print()
複製代碼
運行代碼,獲得結果:
由上面例子可得,若是要在不用的包裏進行擴展,要在調用處 import 擴展的資源。//定義User類,添加一個printUser()函數
class User {
fun printUser(){
println("User")
}
}
//定義User2類,在裏面對User類進行擴展
class User2 {
fun printUser2() {
println("User2")
}
//擴展函數
fun User.print() {
printUser()
printUser2()
}
fun getUser(user: User) {
//調用擴展函數
user.print()
}
}
//調用
User2().getUser(User())
複製代碼
運行代碼,獲得結果:
擴展聲明所在的類的實例稱爲 分發接收者,擴展方法調用所在的接收者類型的實例稱爲 擴展接收者 。對於分發接收者和擴展接收者的成員名字衝突的狀況,擴展接收者優先。若是要引用分發接收者的成員,能夠這樣寫://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 class D {
}
class D1 : D() {
}
open class C {
open fun D.foo() {
println("D.foo in C")
}
open fun D1.foo() {
println("D1.foo in C")
}
fun caller(d: D) {
d.foo() // 調用擴展函數
}
fun caller2(d1: D1) {
d1.foo() // 調用擴展函數
}
}
class C1 : C() {
override fun D.foo() {
println("D.foo in C1")
}
override fun D1.foo() {
println("D1.foo in C1")
}
}
//調用
C().caller(D())
C1().caller(D())
C().caller(D1())
C().caller2(D1())
C1().caller2(D1())
複製代碼
運行代碼,獲得結果:
在Kotlin中,若是有多個地方用到了相同的代碼,能夠用委託來處理。
委託模式是實現繼承一個很好的的替代方式,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()
//運行代碼,打印結果爲 abc
複製代碼
在實際應用中,有不少類的屬性都擁有 getter 和 setter 函數,這些函數大部分都是相同的。Kotlin容許委託屬性,把全部相同的 getter 和 setter 函數放到同一個委託類中,這樣能大大減小冗餘代碼。舉個例子:
class User1 {
var userName: String = ""
get() = field
set(value) {
field = value
}
}
class User2 {
var userName: String = ""
get() = field
set(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 函數,去除了相同的代碼。
Kotlin標準庫中提供了一些有用的委託函數:
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實例自身做爲委託來實現委託屬性,一般用於解析 JSON ,以下所示:
//新建User類,主構函數要求傳入一個Map
class User(val map: Map<String, Any>) {
//聲明一個 String 委託給 map
val 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 map
var 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自身支持並不是常好用的,擴展能使代碼更靈活,委託能實現代碼重用。運用好他們能很好地加快編寫代碼的速度。
參考文獻:
Kotlin語言中文站、《Kotlin程序開發入門精要》
推薦閱讀:
從Java到Kotlin(一)爲何使用Kotlin
從Java到Kotlin(二)基本語法
從Java到Kotlin(三)類和接口
從Java到Kotlin(四)對象與泛型
從Java到Kotlin(五)函數與Lambda表達式
從Java到Kotlin(六)擴展與委託
從Java到Kotlin(七)反射和註解
從Java到Kotlin(八)Kotlin的其餘技術
Kotlin學習資料總彙
更多精彩文章請掃描下方二維碼關注微信公衆號"AndroidCzh":這裏將長期爲您分享原創文章、Android開發經驗等! QQ交流羣: 705929135