[Kotlin] Lambda and Extension

Lambda表達(dá)式 和 Extension 是Kotlin語(yǔ)言中兩個(gè)非常漂亮的語(yǔ)法特性,兩者聯(lián)合起來(lái)使用常常會(huì)觸發(fā)許多令人意想不到的化學(xué)效應(yīng);一旦你也熟悉了這兩個(gè)語(yǔ)法,很容易愛(ài)上他們;

Ok, let's start...

Lambda表達(dá)式基礎(chǔ)

先來(lái)熟悉一下Lambda表達(dá)式的基礎(chǔ)知識(shí)!
Lambda翻譯為中文有匿名函數(shù)的意思,不過(guò),在Kotlin語(yǔ)言中,有專門的匿名函數(shù)語(yǔ)法;所以,二者一定要區(qū)分開(kāi)來(lái),這個(gè)知識(shí)點(diǎn)將在后面進(jìn)行講解。

Lambda表達(dá)式和匿名函數(shù)本質(zhì)上依然是一個(gè)函數(shù),可以認(rèn)為是函數(shù)的一個(gè)變體;所有的函數(shù)都可以轉(zhuǎn)換為L(zhǎng)ambda表達(dá)式,所有的Lambda表達(dá)式也可以轉(zhuǎn)換為函數(shù);先看一個(gè)簡(jiǎn)單的例子:

// 這是一個(gè)簡(jiǎn)單的比較數(shù)字大小的函數(shù)
fun max(i: Int , j: Int): Boolean {
    return i  > j
}
// 轉(zhuǎn)換為L(zhǎng)ambda表達(dá)式
{i: Int , j: Int -> i > j}

從上面的例子我們很容易了解到Lambda表達(dá)式的三個(gè)基本要素:

  • 代碼塊要放在大括號(hào)里面
  • 使用->隔離變量和表達(dá)式
  • 如果->后面的表達(dá)式會(huì)產(chǎn)生新的值,這個(gè)新值將作為該Lambda表達(dá)式的返回值;如果表達(dá)式有多行代碼,并且有多行代碼有返回值,將使用最后一行代碼的返回值作為該Lambda表達(dá)式的返回值;

在使用過(guò)程中記住這三點(diǎn)就可以輕松地使用Lambda表達(dá)式;

理解簡(jiǎn)化版本的Lambda表達(dá)式

在日常使用過(guò)程中,我們經(jīng)常可以看到一些極簡(jiǎn)的Lambda表達(dá)式,有些同學(xué)可能會(huì)誤以為是不是代碼寫(xiě)錯(cuò)了,或者根本就不是Lambda表達(dá)式;其實(shí)不然,來(lái)看一個(gè)簡(jiǎn)單的例子:

// 1) 簡(jiǎn)化表達(dá)式一
val sum: (Int , Int)->Int = {x , y -> x + y}
// 這個(gè)簡(jiǎn)化的Lambda表達(dá)式很容易理解,Kotlin語(yǔ)言支持類型推導(dǎo),通過(guò)前面sum變量指定的參數(shù)類型,很容易推導(dǎo)出x,y均為Int類型,故類型參數(shù)可以省略掉;
// 2) 簡(jiǎn)化表達(dá)式二
ints.filter { it > 0 }
// 這個(gè)簡(jiǎn)化的Lambda表達(dá)式的確有點(diǎn)難以理解,Kotlin語(yǔ)言中有一個(gè)約定,如果Lambda表達(dá)式的參數(shù)只有一個(gè),則可以省略參數(shù)聲明語(yǔ)句;
// 舉一反三
val cl: (Int)->Boolean = {x > 0}
// 這樣寫(xiě)是否可以呢?答案是:不可以!
// 目前的Kotlin編譯器不支持這樣的寫(xiě)法,x必須修改為it;其實(shí),按照邏輯來(lái)講,我認(rèn)為這樣寫(xiě)也是合理的;當(dāng)然,Kotlin團(tuán)隊(duì)可能也是為了規(guī)范單一參數(shù)Lambda表達(dá)式的統(tǒng)一。
匿名函數(shù)

Kotlin語(yǔ)言還支持匿名函數(shù),事實(shí)上在開(kāi)發(fā)過(guò)程中筆者很少使用匿名函數(shù);不過(guò)為了保證文章的完整性,這里也做一個(gè)簡(jiǎn)單介紹!
看一個(gè)簡(jiǎn)單的例子:

// 該匿名函數(shù)主要用于判斷整形x值是否大于0
val f: (Int)->Boolean = fun(x)->x > 0

匿名函數(shù)很好理解,即省略了函數(shù)名的函數(shù)而已!直接在fun關(guān)鍵字后面寫(xiě)形參。
至此,Lambda表達(dá)式的基礎(chǔ)知識(shí)已經(jīng)講解完畢;下面開(kāi)始筆者最喜歡的Kotlin語(yǔ)言特性Extension的講解。

Extension翻譯為中文的意思是擴(kuò)展,以下簡(jiǎn)稱擴(kuò)展
擴(kuò)展特性最早出現(xiàn)在OC語(yǔ)言中,OC的后繼者Swift同樣支持?jǐn)U展特性;遺憾的是,在Java語(yǔ)言中,一直到Java1.8,擴(kuò)展特性依然未獲得支持;幸運(yùn)的是,Kotlin語(yǔ)言支持該特性,Cheers!

Extension基礎(chǔ)知識(shí)

在Java語(yǔ)言中,要對(duì)某個(gè)類進(jìn)行擴(kuò)展,在不改變?cè)蓄惖幕A(chǔ)上,只能使用繼承實(shí)現(xiàn);Kotlin語(yǔ)言可以在不使用繼承的情況下對(duì)類進(jìn)行擴(kuò)展,即添加新的屬性或者方法。

來(lái)看一個(gè)簡(jiǎn)單的例子:

// 使用下面的代碼為String類新增了一個(gè)方法,該方法通過(guò)指定時(shí)間的格式,可以將時(shí)間字符串轉(zhuǎn)化為時(shí)間戳
fun String.toTimeInMillis(pattern: String): Long {
    var date: Date? = null
    try {
        val formatter = SimpleDateFormat(pattern , Locale.getDefault())
        date = formatter.parse(this)
    } catch(e: Exception) {
    }
    return date?.time ?: 0
}
// 在使用的時(shí)候,我們可以直接按照如下的方式調(diào)用:
val time = "2016-11-01 11:30:30".toTimeInMillis("yyyy-MM-dd HH:mm:ss")
// 是不是非常漂亮?-_-

擴(kuò)展中有一個(gè)非常重要的概念就是:Receiver
Receiver又可以分為dispatch receiver和extension receiver

dispatch receiver: 即擴(kuò)展聲明所在的實(shí)體類;換而言之,擴(kuò)展在哪個(gè)類中聲明;,那么,該類就叫做該擴(kuò)展的dispatch receiver

extension receiver: 調(diào)用擴(kuò)展方法的具體的實(shí)體類類型;概念有點(diǎn)類似于多態(tài)。
來(lái)看一個(gè)具體的例子:

// 直接引用官方例子
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() // call the extension function
    }
}

class C1 : C() {
    override fun D.foo() {
        println("D.foo in C1")
    }
    override fun D1.foo() {
        println("D1.foo in C1")
    }
}

C().caller(D())  // prints "D.foo in C"
C1().caller(D()) // prints "D.foo in C1" - dispatch receiver is resolved virtually
C().caller(D1()) // prints "D.foo in C" - extension receiver is resolved statically

來(lái)做一個(gè)簡(jiǎn)單的分析:
<code>C1().caller(D())</code> : 這句代碼中,dispatch receiver是C1, extension receiver是D1。實(shí)際調(diào)用的方法是C1類中聲明的D的擴(kuò)展方法foo。這說(shuō)明dispatch receiver是動(dòng)態(tài)執(zhí)行的,或者說(shuō)會(huì)根據(jù)運(yùn)行時(shí)類型決定調(diào)用的擴(kuò)展方法類型。
再來(lái)看這行代碼 <code>C().caller(D1())</code> : 這句代碼中,dispatch receiver是C,extension receiver是D1,實(shí)際調(diào)用的方法確實(shí)C類中聲明的D的擴(kuò)展方法。這里其實(shí)同樣說(shuō)明了上面的道理,即實(shí)際調(diào)用的擴(kuò)展方法類型是動(dòng)態(tài)執(zhí)行的,而extension receiver是靜態(tài)解析的,即不會(huì)根據(jù)編譯時(shí)類型動(dòng)態(tài)改變。

按照上面的分析理解,的確有點(diǎn)抽象,為此筆者使用下面一句話概括:
** 實(shí)際調(diào)用的擴(kuò)展方法由dispatch receiver決定,即最終尋找擴(kuò)展方法的順序應(yīng)該是從具體的dispatch receiver中查找該方法,如果沒(méi)有找到則往父類中找。**

實(shí)際使用過(guò)程中,如果出現(xiàn)比較復(fù)雜的receiver類型,請(qǐng)來(lái)查閱這篇文章的這個(gè)部分。

尾隨閉包

這個(gè)概念在Kotlin的官方文檔中并沒(méi)有明確說(shuō)明,這里我引用Swift語(yǔ)言中的一個(gè)相同概念來(lái)表示它。這里可以認(rèn)為是一個(gè)函數(shù)寫(xiě)法的變種,即:如果函數(shù)的最后一個(gè)參數(shù)是Lambda表達(dá)式,函數(shù)調(diào)用的時(shí)候參數(shù)可以寫(xiě)到函數(shù)的括號(hào)后面;看下面的例子:

fun lock(force: Boolean , lock: ()->Unit) {
}

// 可以用下面的方式調(diào)用:
lock(false) {
}

至此,Lambda和Extension的基礎(chǔ)知識(shí)點(diǎn)就講完了;來(lái)看一看在Android開(kāi)發(fā)中,他們發(fā)揮怎樣的作用!

舉一個(gè)常見(jiàn)的例子,在Android開(kāi)發(fā)中,經(jīng)常需要在Activity或者Fragment中使用<code>Toast.makeText(this , Hello,world" , Toast.LENGTH_LONG).show()</code>

為了簡(jiǎn)化調(diào)用,很多同學(xué)使用下面的方式簡(jiǎn)化調(diào)用:

class ToastUtil {
    public static void toast(Context context , Charsequence text , int length) {
        Toast.makeText(context , text , length)
    }
}

在Kotlin語(yǔ)言中,可以通過(guò)擴(kuò)展使用非常優(yōu)雅的方式解決這個(gè)問(wèn)題,看代碼:

fun Activity.toast(text: Charsequence? , length: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this , text , length).show()
}
// 通過(guò)上面的擴(kuò)展方法,在Activity中就可以直接使用
toast("Hello, world") 

同樣,也可以為Fragment添加擴(kuò)展:

fun Fragment.toast(text: Charsequence? , length: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(activity , text , length).show()
}

怎么樣,是不是比Java語(yǔ)言的實(shí)現(xiàn)方式優(yōu)雅了許多?

不服?來(lái)看點(diǎn)更屌的!
在Android開(kāi)發(fā)中,很多資源使用后必須記得關(guān)閉,比如數(shù)據(jù)庫(kù)、IO流等等。在日常使用中,常常有人會(huì)忘記手動(dòng)調(diào)用close方法關(guān)閉資源,為了避免這個(gè)問(wèn)題,Java語(yǔ)言的解決方案可以使用一個(gè)方法幫助開(kāi)啟和關(guān)閉資源。以數(shù)據(jù)庫(kù)為例,看下面的代碼:

// Java語(yǔ)言實(shí)現(xiàn)
public void addUser(User user) {
    SQLiteDatabase db = SQLiteOpenHelper.get(xxx);
    // 這里寫(xiě)邏輯
    db.close
}

Java語(yǔ)言的這種實(shí)現(xiàn)方式的確可以解決這個(gè)問(wèn)題,但不夠完全,它僅僅解決了addUser的問(wèn)題;如果是刪除用戶,就必須新加一個(gè)方法去解決這個(gè)問(wèn)題。有沒(méi)有一種通用的方式來(lái)解決這個(gè)問(wèn)題呢?答案是:有!不過(guò)Java語(yǔ)言實(shí)現(xiàn)起來(lái)比較麻煩,這個(gè)技術(shù)有一個(gè)專業(yè)名詞叫AOP,即面向切面編程,需要通過(guò)Java語(yǔ)言的反射特性來(lái)實(shí)現(xiàn)該邏輯。使用反射實(shí)現(xiàn)通常是一個(gè)比較繁瑣的邏輯了,在Kotlin語(yǔ)言中我們可以使用Lambda表達(dá)式和擴(kuò)展輕松實(shí)現(xiàn),看下面的代碼:

fun use(func: SQLiteDataBase.()->Unit) {
    func.invoke()
    close()
}
use {
    // 這里可以使用SQLiteDataBase的任意代碼
}

通過(guò)上面的方法在使用完SQLite數(shù)據(jù)庫(kù)后,就幫助你自動(dòng)關(guān)閉了。這種使用方法還可以進(jìn)行延伸,比如:

fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try {
        return body()
    } finally {
        lock.unlock()
    }
}

在Android開(kāi)發(fā)中,對(duì)話框是必不可少的組件;可是,每次創(chuàng)建對(duì)話框確實(shí)一件比較麻煩的事情;為此,Java程序員常常在Android代碼中使用DialogFactory創(chuàng)建各種對(duì)話框,這的確減少了很多繁瑣的代碼。可并不是特別漂亮,不妨來(lái)看一看Kotlin語(yǔ)言的解決方案:

fun Context.dialog(message: CharSequence , init: (Dialog.()->Unit)? = null): Dialog {
    val dialog = Dialog(this)
    dialog.message(message)
    if(null != init) {
        dialog.init()
    }
    return dialog
}

看起來(lái)并沒(méi)有什么特殊之處;可是,在使用的時(shí)候,卻看起來(lái)非常漂亮,請(qǐng)看下面的調(diào)用方法:

// 假設(shè)在Activity類中
dialog("Hello, kotlin") {
    positiveButton("確定") {
        dissmiss()
    }
    // 這里可以使用Dialog類的所有api
    setCanceledOnTouchOutside(false)
}.show()

歡迎加入Kotlin交流群

如果你也喜歡Kotlin語(yǔ)言,歡迎加入我的Kotlin交流群: 329673958 ,一起來(lái)參與Kotlin語(yǔ)言的推廣工作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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