從Java到Kotlin(六)

擴展與委託html

目錄

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 函數時,能夠這樣: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()

1.2擴展屬性

擴展屬性是對屬性的擴展,以下所示: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 的值。

1.3擴展伴生對象

除了擴展函數和擴展屬性外,還能夠對伴生對象進行擴展,代碼以下:ui

class User {
    companion object {
    }
}

//擴展伴生對象
fun User.Companion.foo() {
    println("伴生對象擴展")
}

//調用
User.foo()
複製代碼

運行代碼,獲得結果

1.4擴展的做用域

  • 在不一樣包裏進行擴展
    上面的代碼都是在同一個包裏進行擴展,若是在不一樣包裏要進行擴展,就要用import來導入資源了,以下所示:
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 並在子類中覆蓋。這意味着這些函數的分發對於分發接收者類型是虛擬的,但對於擴展接收者類型是靜態的。
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())
複製代碼

運行代碼,獲得結果:


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()

//運行代碼,打印結果爲 abc
複製代碼

2.2 委託屬性

在實際應用中,有不少類的屬性都擁有 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 函數,去除了相同的代碼。

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 委託給 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

相關文章
相關標籤/搜索