共计 4379 个字符,预计需要花费 11 分钟才能阅读完成。
download: 深刻 Go 底层原理,重写 Redis 中间件实战【网盘分享】
揭秘 Kotlin 1.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 ${this@LogContext.name}")
writeFile("write message in file ${this@FileContext.name}")
}
复制代码
正如你所见,在 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 关键字会导致可读性变差,咱们可能通过接口隔离的形式来解决这个问题
当咱们重写带有上下文承受者的函数时,必须申明为雷同类型的上下文承受者