kotlin1.6.20新功能context receivers使用技巧揭秘-kb88凯时官网登录

来自:网络
时间:2022-12-26
阅读:
免费资源网 - https://freexyz.cn/
目录

前言

这篇文章我们一起来聊一下 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 关键字会导致可读性变差,我们可以通过接口隔离的方式来解决这个问题
  • 当我们重写带有上下文接受者的函数时,必须声明为相同类型的上下文接受者

以上就是kotlin1.6.20功能context receivers使用技巧揭秘的详细内容,更多关于kotlin1.6.20功能context receivers的资料请关注其它相关文章!

免费资源网 - https://freexyz.cn/
返回顶部
顶部
网站地图