Kotlin1.6.20新功能Context?Receivers使用技巧揭秘
前言
這篇文章我們一起來聊一下 Kotlin 1.6.20 的新功能 Context Receivers,來看看它為我們解決了什么問題。
通過這篇文章將會學習到以下內(nèi)容:
- 擴展函數(shù)的局限性
- 什么是 Context Receivers,以及如何使用
- Context Receivers 解決了什么問題
- 引入 Context Receivers 會帶來新的問題,我們?nèi)绾谓鉀Q
- Context Receivers 應(yīng)用范圍及注意事項
擴展函數(shù)的局限性
在 Kotlin 中接受者只能應(yīng)用在擴展函數(shù)或者帶接受者 lambda 表達式中, 如下所示。
class Context {
var density = 0f
}
// 擴展函數(shù)
inline fun Context.px2dp(value: Int): Float = value.toFloat() / density
接受者是 fun 關(guān)鍵字之后和點之前的類型 Context,這里隱藏了兩個知識點。
- 我們可以像調(diào)用內(nèi)部函數(shù)一樣,調(diào)用擴展函數(shù) px2dp(),通常結(jié)合 Kotlin 作用域函數(shù) with , run , apply 等等一起使用。
with(Context()) {
px2dp(100)
}
- 在擴展函數(shù)內(nèi)部,我們可以使用 this 關(guān)鍵字,或者隱藏關(guān)鍵字隱式訪問內(nèi)部的成員函數(shù),但是我們不能訪問私有成員
擴展函數(shù)使用起來很方便,我們可以對系統(tǒng)或者第三方庫進行擴展,但是也有局限性。
- 只能定義一個接受者,因此限制了它的可組合性,如果有多個接受者只能當做參數(shù)傳遞。比如我們調(diào)用 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 中接受者只能應(yīng)用在擴展函數(shù)或者帶接受者 lambda 表達式中,卻不能在普通函數(shù)中使用,失去了靈活性
Context Receivers 的出現(xiàn)帶來新的可能性,它通過了組合的方式,將多個上下文接受者合并在一起,靈活性更高,應(yīng)用范圍更廣。
什么是 Context Receivers
Context Receivers 用于表示一個基本約束,即在某些情況下需要在某些范圍內(nèi)才能完成的事情,它更加的靈活,可以通過組合的方式,組織上下文,將系統(tǒng)或者第三方類組合在一起,實現(xiàn)更多的功能。
如果想在項目中使用 Context Receivers,需要將 Kotlin 插件升級到 1.6.20 ,并且在項目中開啟才可以使用。
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.6.20'
}
// ......
kotlinOptions {
freeCompilerArgs = ["-Xcontext-receivers"]
}
如何使用 Context Receivers
當我們完成上述配置之后,就可以在項目中使用 Context Receivers,現(xiàn)在我們將上面的案例改造一下。
context(LogContext, FileContext)
fun printf() {
with(Context()) {
val dp = px2dp(100)
logContext.logcat("print ${dp} in logcat")
fileContext.writeFile("write ${dp} in file")
}
}
我們在 printf() 函數(shù)上,使用 context() 關(guān)鍵字,在 context() 關(guān)鍵字括號中,聲明上下文接收者類型的列表,多個類型用逗號分隔。但是列出的類型不允許重復,它們之間不允許有子類型關(guān)系。
通過 context() 關(guān)鍵字來限制它的作用范圍,在這個函數(shù)中,我們可以調(diào)用上下文 LogContext 、 FileContext 內(nèi)部的方法,但是使用的時候,只能通過 Kotlin 作用域函數(shù)嵌套來傳遞多個接受者,也許在未來可能會提供更加優(yōu)雅的方式。
with(LogContext()) {
with(FileContext()) {
printf("I am DHL")
}
}
引入 Context Receivers 導致可讀性問題
如果我們在 LogContext 和 FileContext 中聲明了多個相同名字的變量或者函數(shù),我們只能通過 this@Lable 語句來解決這個問題。
context(LogContext, FileContext)
fun printf(message: String) {
logcat("print message in logcat ${this@LogContext.name}")
writeFile("write message in file ${this@FileContext.name}")
}
正如你所見,在 LogContext 和 FileContext 中都有一個名為 name 的變量,我們只能通過 this@Lable 語句來訪問,但是這樣會引入一個新的問題,如果有大量的同名的變量或者函數(shù),會導致 this 關(guān)鍵字分散到處都是,造成可讀性很差。所以我們可以通過接口隔離的方式,來解決這個問題。
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 關(guān)鍵字導致的可讀性差的問題,使用的時候需要實例化接口。
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 應(yīng)用范圍及注意事項
當我們重寫帶有上下文接受者的函數(shù)時,必須聲明為相同類型的上下文接受者。
interface Canvas
interface Shape {
context(Canvas)
fun draw()
}
class Circle : Shape {
context(Canvas)
override fun draw() {
}
}
我們重寫了 draw() 函數(shù),聲明的上下文接受者必須是相同的,Context Receivers 不僅可以作用在擴展函數(shù)、普通函數(shù)上,而且還可以作用在類上。
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() 關(guān)鍵字,我們就可以在 LogHelp 范圍內(nèi)任意的地方使用 LogContext 或者 FileContex。
val logHelp = with(logContext) {
with(fileContext) {
LogHelp()
}
}
logHelp.printf("I am DHL")
Context Receivers 除了作用在擴展函數(shù)、普通函數(shù)、類上,還可以作用在屬性 getter 和 setter 以及 lambda 表達式上。
context(View)
val Int.dp get() = this.toFloat().dp
// lambda 表達式
fun save(block: context(LogContextInterface) () -> Unit) {
}
最后我們來看一下,來自社區(qū) 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"
}
}
}
總結(jié)
- Context Receivers 提供一個基本的約束,可以在指定范圍內(nèi),通過組合的方式實現(xiàn)更多的功能
- Context Receivers 可以作用在擴展函數(shù)、普通函數(shù)、類、屬性 getter 和 setter 、 lambda 表達式
- Context Receivers 允許在不需要繼承的情況,通過組合的方式,組織上下文,將系統(tǒng)或者第三方類組合在一起,實現(xiàn)更多的功能
- 通過 context() 關(guān)鍵字聲明,在 context() 關(guān)鍵字括號中,聲明上下文接收者類型的列表,多個類型用逗號分隔
- 如果大量使用 this 關(guān)鍵字會導致可讀性變差,我們可以通過接口隔離的方式來解決這個問題
- 當我們重寫帶有上下文接受者的函數(shù)時,必須聲明為相同類型的上下文接受者
以上就是Kotlin1.6.20功能Context Receivers使用技巧揭秘的詳細內(nèi)容,更多關(guān)于Kotlin1.6.20功能Context Receivers的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android網(wǎng)絡(luò)技術(shù)HttpURLConnection詳解
這篇文章主要為大家詳細介紹了Android網(wǎng)絡(luò)技術(shù)HttpURLConnection的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07
Android 懸浮窗權(quán)限各機型各系統(tǒng)適配大全(總結(jié))
這篇文章主要介紹了Android 懸浮窗權(quán)限各機型各系統(tǒng)適配大全(總結(jié)),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08
Android中使用ZXing生成二維碼(支持添加Logo圖案)
ZXing是谷歌的一個開源庫,可以用來生成二維碼、掃描二維碼。接下來通過本文給大家介紹Android中使用ZXing生成二維碼(支持添加Logo圖案),需要的朋友參考下2017-01-01
android TextView設(shè)置中文字體加粗實現(xiàn)方法
android TextView設(shè)置中文字體加粗如何實現(xiàn),接下來介紹實現(xiàn)方法,有需要的朋友可以參考下2013-01-01
Android中使用AsyncTask實現(xiàn)下載文件動態(tài)更新進度條功能
這篇文章主要介紹了AsyncTask用法解析-下載文件動態(tài)更新進度條,需要的朋友可以參考下2017-08-08
Android UniversalVideoView實現(xiàn)視頻播放器
這篇文章主要為大家詳細介紹了Android UniversalVideoView實現(xiàn)視頻播放器,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04

