Kotlin 有趣的擴展函數(shù)

Kotlin

是一個目標為Java平臺上的新的編程語言。它是簡潔、安全、實用和可以跟Java互操作。它能用到所有Java可以用到的地方: 服務端開發(fā),Android應用開發(fā),或者更多。Kotlin可以和所有存在的Java庫和框架一起良好工作,有Java一樣的效率

在kotlin中,函數(shù)和對象一樣,都是“一等公民”,這也就表示在kotlin中,函數(shù)可以做變量能做的事情,如可以存儲在變量與數(shù)據(jù)結構中、或是作為參數(shù)傳遞給其他高階函數(shù)并且也可以作為高階函數(shù)的返回值、也可以像其他任何非函數(shù)值一樣調用函數(shù)

Kotlin擴展函數(shù)

Kotlin支持在不繼承或者使用裝飾模式的情況下對現(xiàn)有的類進行函數(shù)擴展,擴展后我們可以直接通過相應類調用的該擴展函數(shù)。函數(shù)中可以直接使用this訪問到對象的成員變量

定義一個擴展函數(shù)

在Android中我們完成一個Toast操作如下,大部分情況我們通過定義utils來方便調用,在用kotlin我們就可以采用擴展函數(shù)的形式定義toast操作

// 擴展函數(shù)
inline fun Context.toast(msg: String) {
    Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
}
inline fun Fragment.toast(msg: String) {
    Toast.makeText(activity, msg, Toast.LENGTH_LONG).show()
}
// 調用
toast("hello toast")

在這背后其實,kotlin做的也是將我們定義的擴展函數(shù)轉換成一個工具類的形式,方法參數(shù)傳入被擴展對象。只不過在使用上對開發(fā)者來說是透明的

標準庫中的擴展函數(shù)

Kotlin為開發(fā)者提供了許多標準擴展函數(shù),下面一起看看let、apply、also、run這四個擴展函數(shù)的具體定義以及使用

let擴展函數(shù)

方法定義

  • T泛型:目標擴展對象
  • R泛型:函數(shù)參數(shù)的返回值
  • block函數(shù):接口參數(shù)為T對象,返回值為R
  • 執(zhí)行block函數(shù)并返回其結果
public inline fun <T, R> T.let(block: (T) -> R): R {
    return block(this)
}

使用例子

        // 對變量進行判空、流程規(guī)范
        listView?.let {
            it.setFooterDividersEnabled(true)
            it.setSelectionAfterHeaderView()
            it.divider = null
            var adapter = ArrayAdapter<String>(it.context, android.R.layout.simple_list_item_1)
            it.adapter = adapter
            adapter
        }?.let { 
            it.addAll(listOf("item1", "item1"))
        }
apply擴展函數(shù)

方法定義

  • T泛型:目標擴展對象
  • block函數(shù):仍然為T的擴展函數(shù),因此在函數(shù)體內可使用this或者直接引用目標對象的成員變量
  • 執(zhí)行block函數(shù)并返回目標對象自身
public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}

使用例子

        // 對象的構建,或者連續(xù)調用其多個方法,可省略前綴
        var paint = Paint().apply {
            isAntiAlias = true
            color = Color.WHITE
            xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
        }
also擴展函數(shù)

方法定義

  • T泛型:目標擴展對象
  • block函數(shù):接口參數(shù)為T對象,返回值為空
  • 執(zhí)行block函數(shù)并返回目標對象自身
public inline fun <T> T.also(block: (T) -> Unit): T {
    block(this)
    return this
}

使用例子

      stuInfo.also {
            it.append(stu?.name)
            it.append(stu?.age)
            it.append(stu?.code)
        }.toString()
run擴展函數(shù)

方法定義

  • T泛型:目標擴展對象
  • R泛型:函數(shù)參數(shù)的返回值
  • block函數(shù):仍然為T的擴展函數(shù),因此在函數(shù)體內可使用this或者直接引用目標對象的成員變量
  • 執(zhí)行block函數(shù)并返回其結果
public inline fun <T, R> T.run(block: T.() -> R): R {
    return block()
}

使用例子

        file?.run { 
            listFiles()
        }?.run { 
            forEach { 
                it.delete()
            }
        }
關于inline關鍵字

Kotlin天生支持函數(shù)式編程,高階函數(shù)和lambda是其一大特色
使用高階函數(shù)會帶來一些運行時間效率的損失:實際上調用一個lambda表達式編譯時會生成一個匿名內部類并創(chuàng)建其對象,調用其函數(shù)來實現(xiàn)。但是如果使用inline關鍵字的話,當你調用一個inline function的時候,編譯器不會產(chǎn)生一次函數(shù)調用,而是會在每次調用時,將inline function中的代碼直接嵌入到調用處。

let、apply、also、run總結

從上面的各個例子可以可看到這幾個擴展函數(shù)非常相似,只是在函數(shù)體中對于目標對象的引用方式以及函數(shù)返回值這兩點的不同

擴展函數(shù) 引用目標對象方式 返回值
let it 函數(shù)返回值
apply this 對象自身
also it 對象自身
run this 函數(shù)返回值

Android使用擴展函數(shù)

在開發(fā)過程中我們可以通過定義擴展函數(shù)提供各種更加高效的編碼。Android KTX 是一組 Kotlin 擴展程序,屬于 Android Jetpack 系列。它優(yōu)化了供 Kotlin 使用的 Jetpack 和 Android 平臺 API。Android KTX 旨在讓您利用 Kotlin 語言功能(例如擴展函數(shù)/屬性、lambda、命名參數(shù)和參數(shù)默認值),以更簡潔、更愉悅、更慣用的方式使用 Kotlin 進行 Android 開發(fā)

下面我們來通過Android KTX中的例子來看看官方為我們定義的擴展函數(shù)

Animator動畫監(jiān)聽

在不使用擴展函數(shù)時,我們監(jiān)聽動畫是這樣的

    val animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 100f)
    animator.addListener {
        object : Animator.AnimatorListener {
            override fun onAnimationEnd(animation: Animator?) {
            }

            override fun onAnimationCancel(animation: Animator?) {
            }

            override fun onAnimationStart(animation: Animator?) {
            }

            override fun onAnimationRepeat(animation: Animator?) {
            }

        }
    }

然后我們使用androidx.core.animation.Animator.kt中的擴展函數(shù)

val animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 100f)
    animator.addListener(onCancel = {
        // something
    })

果然簡潔舒服許多,是怎么實現(xiàn)的呢。我們看看Animator.kt的源碼

  • 定義Animator的擴展函數(shù)addListener
  • 接收onEnd、onStart、onCancel、onRepeat并指定默認值為空函數(shù)
  • 擴展函數(shù)中創(chuàng)建AnimatorListener的匿名對象并將函數(shù)參數(shù)賦值給對應函數(shù)
  • 那么在使用時我們只需要指定我們需要的函數(shù)即可
inline fun Animator.addListener(
    crossinline onEnd: (animator: Animator) -> Unit = {},
    crossinline onStart: (animator: Animator) -> Unit = {},
    crossinline onCancel: (animator: Animator) -> Unit = {},
    crossinline onRepeat: (animator: Animator) -> Unit = {}
): Animator.AnimatorListener {
    val listener = object : Animator.AnimatorListener {
        override fun onAnimationRepeat(animator: Animator) = onRepeat(animator)
        override fun onAnimationEnd(animator: Animator) = onEnd(animator)
        override fun onAnimationCancel(animator: Animator) = onCancel(animator)
        override fun onAnimationStart(animator: Animator) = onStart(animator)
    }
    addListener(listener)
    return listener
}
SharedPreferences

正常情況我們是這樣使用它的

        val sp = getSharedPreferences("test", Context.MODE_PRIVATE)
        sp.edit().putString("name", "liu").commit()
        sp.edit().putInt("age", 20).commit()
        sp.edit().putBoolean("isMale", true).commit()

我們用擴展參數(shù)是這樣的

        sp.edit {
            putString("name", "liu")
            putInt("age", 20)
            putBoolean("isMale", true)
        }

擴展函數(shù)定義如下:

inline fun SharedPreferences.edit(
    commit: Boolean = false,
    action: SharedPreferences.Editor.() -> Unit
) {
    val editor = edit()
    action(editor)
    if (commit) {
        editor.commit()
    } else {
        editor.apply()
    }
}

  • 定義SharedPreferences 的edit擴展函數(shù)
  • commit參數(shù):是否及時commit提交到磁盤文件
  • action參數(shù):是SharedPreferences.Editor的擴展函數(shù),需要傳入Editor
自定義擴展函數(shù)
// 定義EditText擴展函數(shù),方便監(jiān)聽TextChange
inline fun EditText.onTextChange(
        crossinline afterChanged: (s: Editable?) -> Unit = {},
        crossinline beforeChanged: (s: CharSequence?, start: Int, count: Int, after: Int) -> Unit = { s, start, couunt, after -> },
        crossinline onChanged: (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit = { s, start, before, count -> }
) {
    val listener = object : TextWatcher {
        override fun afterTextChanged(s: Editable?) = afterChanged(s)
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = beforeChanged(s, start, count, after)
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = onChanged(s, start, before, count)
    }
    addTextChangedListener(listener)
}

擴展函數(shù)中我們指定了參數(shù)的默認值,所以在使用時我們可以指定我們需要的函數(shù)即可。比如我們只需要監(jiān)聽onTextChanged

        editText.onTextChange(
                onChanged = { c: CharSequence?, i: Int, i1: Int, i2: Int ->
                }
        )

看著還是麻煩,我們再加個擴展函數(shù)

inline fun EditText.onChanged(
        crossinline onChanged: (s: CharSequence?, start: Int, before: Int, count: Int) -> Unit = { _, _, _, _ -> }) {
    onTextChange(onChanged = onChanged)
}

用的時候,我們單獨調用onChanged方法

        editText.onChanged { s, start, before, count ->

        }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容