Kotlin1.6.20新功能Context Receivers使用技巧揭秘
前言
這篇文章我們一起來聊一下 Kotlin 1.6.20 的新功能 Context Receivers,來看看它為我們解決瞭什麼問題。
通過這篇文章將會學習到以下內容:
- 擴展函數的局限性
- 什麼是 Context Receivers,以及如何使用
- Context Receivers 解決瞭什麼問題
- 引入 Context Receivers 會帶來新的問題,我們如何解決
- Context Receivers 應用范圍及註意事項
擴展函數的局限性
在 Kotlin 中接受者隻能應用在擴展函數或者帶接受者 lambda 表達式中, 如下所示。
class Context { var density = 0f } // 擴展函數 inline fun Context.px2dp(value: Int): Float = value.toFloat() / density
接受者是 fun 關鍵字之後和點之前的類型 Context,這裡隱藏瞭兩個知識點。
- 我們可以像調用內部函數一樣,調用擴展函數 px2dp(),通常結合 Kotlin 作用域函數 with , run , apply 等等一起使用。
with(Context()) { px2dp(100) }
- 在擴展函數內部,我們可以使用 this 關鍵字,或者隱藏關鍵字隱式訪問內部的成員函數,但是我們不能訪問私有成員
擴展函數使用起來很方便,我們可以對系統或者第三方庫進行擴展,但是也有局限性。
- 隻能定義一個接受者,因此限制瞭它的可組合性,如果有多個接受者隻能當做參數傳遞。比如我們調用 px2dp() 方法的同時,往 logcat 和 file 中寫入日志。
class LogContext { fun logcat(message: Any){} } class FileContext { fun writeFile(message: Any) {} } fun printf(logContext: LogContext, fileContext: FileContext) { with(Context()) { val dp = px2dp(100) logContext.logcat("print ${dp} in logcat") fileContext.writeFile("write ${dp} in file") } }
- 在 Kotlin 中接受者隻能應用在擴展函數或者帶接受者 lambda 表達式中,卻不能在普通函數中使用,失去瞭靈活性
Context Receivers 的出現帶來新的可能性,它通過瞭組合的方式,將多個上下文接受者合並在一起,靈活性更高,應用范圍更廣。
什麼是 Context Receivers
Context Receivers 用於表示一個基本約束,即在某些情況下需要在某些范圍內才能完成的事情,它更加的靈活,可以通過組合的方式,組織上下文,將系統或者第三方類組合在一起,實現更多的功能。
如果想在項目中使用 Context Receivers,需要將 Kotlin 插件升級到 1.6.20 ,並且在項目中開啟才可以使用。
plugins { id 'org.jetbrains.kotlin.jvm' version '1.6.20' } // ...... kotlinOptions { freeCompilerArgs = ["-Xcontext-receivers"] }
如何使用 Context Receivers
當我們完成上述配置之後,就可以在項目中使用 Context Receivers,現在我們將上面的案例改造一下。
context(LogContext, FileContext) fun printf() { with(Context()) { val dp = px2dp(100) logContext.logcat("print ${dp} in logcat") fileContext.writeFile("write ${dp} in file") } }
我們在 printf() 函數上,使用 context() 關鍵字,在 context() 關鍵字括號中,聲明上下文接收者類型的列表,多個類型用逗號分隔。但是列出的類型不允許重復,它們之間不允許有子類型關系。
通過 context() 關鍵字來限制它的作用范圍,在這個函數中,我們可以調用上下文 LogContext 、 FileContext 內部的方法,但是使用的時候,隻能通過 Kotlin 作用域函數嵌套來傳遞多個接受者,也許在未來可能會提供更加優雅的方式。
with(LogContext()) { with(FileContext()) { printf("I am DHL") } }
引入 Context Receivers 導致可讀性問題
如果我們在 LogContext 和 FileContext 中聲明瞭多個相同名字的變量或者函數,我們隻能通過 this@Lable 語句來解決這個問題。
context(LogContext, FileContext) fun printf(message: String) { logcat("print message in logcat ${[email protected]}") writeFile("write message in file ${[email protected]}") }
正如你所見,在 LogContext 和 FileContext 中都有一個名為 name 的變量,我們隻能通過 this@Lable 語句來訪問,但是這樣會引入一個新的問題,如果有大量的同名的變量或者函數,會導致 this 關鍵字分散到處都是,造成可讀性很差。所以我們可以通過接口隔離的方式,來解決這個問題。
interface LogContextInterface{ val logContext:LogContext } interface FileContextInterface{ val fileContext:FileContext } context(LogContextInterface, FileContextInterface) fun printf(message: String) { logContext.logcat("print message in logcat ${logContext.name}") fileContext.writeFile("write message in file ${fileContext.name}") }
通過接口隔離的方式,我們就可以解決 this 關鍵字導致的可讀性差的問題,使用的時候需要實例化接口。
val logContext = object : LogContextInterface { override val logContext: LogContext = LogContext() } val fileContext = object : FileContextInterface { override val fileContext: FileContext = FileContext() } with(logContext) { with(fileContext) { printf("I am DHL") } }
Context Receivers 應用范圍及註意事項
當我們重寫帶有上下文接受者的函數時,必須聲明為相同類型的上下文接受者。
interface Canvas interface Shape { context(Canvas) fun draw() } class Circle : Shape { context(Canvas) override fun draw() { } }
我們重寫瞭 draw() 函數,聲明的上下文接受者必須是相同的,Context Receivers 不僅可以作用在擴展函數、普通函數上,而且還可以作用在類上。
context(LogContextInterface, FileContextInterface) class LogHelp{ fun printf(message: String) { logContext.logcat("print message in logcat ${logContext.name}") fileContext.writeFile("write message in file ${fileContext.name}") } }
在類 LogHelp 上使用瞭 context() 關鍵字,我們就可以在 LogHelp 范圍內任意的地方使用 LogContext 或者 FileContex。
val logHelp = with(logContext) { with(fileContext) { LogHelp() } } logHelp.printf("I am DHL")
Context Receivers 除瞭作用在擴展函數、普通函數、類上,還可以作用在屬性 getter 和 setter 以及 lambda 表達式上。
context(View) val Int.dp get() = this.toFloat().dp // lambda 表達式 fun save(block: context(LogContextInterface) () -> Unit) { }
最後我們來看一下,來自社區 Context Receivers 實踐的案例,擴展 Json 工具類。
fun json(build: JSONObject.() -> Unit) = JSONObject().apply { build() } context(JSONObject) infix fun String.by(build: JSONObject.() -> Unit) = put(this, JSONObject().build()) context(JSONObject) infix fun String.by(value: Any) = put(this, value) fun main() { val json = json { "name" by "Kotlin" "age" by 10 "creator" by { "name" by "JetBrains" "age" by "21" } } }
總結
- Context Receivers 提供一個基本的約束,可以在指定范圍內,通過組合的方式實現更多的功能
- Context Receivers 可以作用在擴展函數、普通函數、類、屬性 getter 和 setter 、 lambda 表達式
- Context Receivers 允許在不需要繼承的情況,通過組合的方式,組織上下文,將系統或者第三方類組合在一起,實現更多的功能
- 通過 context() 關鍵字聲明,在 context() 關鍵字括號中,聲明上下文接收者類型的列表,多個類型用逗號分隔
- 如果大量使用 this 關鍵字會導致可讀性變差,我們可以通過接口隔離的方式來解決這個問題
- 當我們重寫帶有上下文接受者的函數時,必須聲明為相同類型的上下文接受者
以上就是Kotlin1.6.20功能Context Receivers使用技巧揭秘的詳細內容,更多關於Kotlin1.6.20功能Context Receivers的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Android Studio中Logcat寫入和查看日志
- Kotlin與Java的區別詳解
- Java與Kotlin互調原理
- Kotlin編程條件控制示例詳解
- 秒懂Kotlin之Java工程師快速掌握Kotlin的技巧