解密Kotlin 1.6.20 新功能 Context Receivers

Kotlin中的上下文接收者:新特性解析 Kotlin作为现代多范式编程语言,一直在不断演进其表达能力。上下文接收者是Kotlin 1.6.20中引入的实验性特性(在1.7.20中稳定),旨在解决传统扩展函数和作用域函数在复杂场景下的局限性。本文将全面解析这一特性的设计思想、实现原理和最佳实践。文章首先介绍上下文接收者的基本概念,然后深入其类型系统实现,接着通过实际案例展示应用场景,最后讨论其局限性和未来发展方向。上下文接收者(Context Receiver):函数或属性可以访问的隐式上下文对象作用域函数(Scope Function) 阅读详情

这篇文章我们一起来聊一下 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.21编译环境 自己存一下 立即下载

相关推荐

RedPitaya火龙果板基础操作

火龙果板基础操作一级目录二级目录三级目录 一级目录 二级目录 三级目录

qq_41523340的博客 9731

玩弄 Kotlin 的Context Receivers

Kotlin 在 1.6.20 版本中加入了 Context Receivers 的想法。在这篇文章中,我想玩弄它们以了解它们的用处。请注意,如果您想一起玩,则需要使用context-receivers标志进行编译。Context Receivers背后的主要思想是将附加参数传递给函数,而无需显式执行。

xiaokangss的博客 464

4090显卡实测:PointPillars在KITTI数据集上的3D目标检测复现全流程(附避坑指南)

本文详细记录了在NVIDIA RTX 4090显卡上,从环境搭建、数据处理到模型训练与评估,完整复现PointPillars算法在KITTI数据集上进行3D目标检测的全流程。重点分享了版本配置、数据预处理、性能调优及常见问题的避坑指南,帮助开发者高效利用硬件资源,成功跑通并理解这一经典3D检测项目。

purple的博客 915

解密Kotlin1.6.20新功能ContextReceiver

一起来聊一下 Kotlin 1.6.20 的新功能`Context Receivers,来看看它为我们解决了什么问题,以及如何使用

公众号:ByteCode 869

vscode远程连接deepin出错:过程试图写入的管道不存在的解决方法

背景:昨天我用vscode使用ssh远程开发还处于正常状态,今天刚打开电脑,并启动deepin,再次使用vscode重新连接就出现异常。意思就是22号默认端口连接被拒绝,所以要先确定ssh是否连接。3.重新进入vscode里面,重新连接,如图成功连接。,查看port后面的端口号是不是22号。1.检查sshd是否启动。

qq_43573047的博客 2946

手把手教你学Simulink——基于电机驱动系统的场景实例:永磁同步电机(PMSM)在电动汽车中的矢量控制仿真

随着全球对环境保护的关注增加,电动汽车(Electric Vehicle, EV)因其环保特性而受到广泛关注。永磁同步电机(Permanent Magnet Synchronous Motor, PMSM)由于其高效率、高功率密度和良好的动态响应性能,在电动汽车驱动系统中得到了广泛应用。矢量控制(Field-Oriented Control, FOC)是实现PMSM高性能控制的关键技术之一,它能够实现对电机转矩和磁场的独立控制,从而达到类似直流电机的控制效果。本文将详细介绍如何使用来构建一个。

MHD0815的博客 1万+

Kotlin扩展方法进化之Context Receiver

黎赵太郎的博客地址:https://lizhaotailang.works/about/为什么会有 Context Receiver我们都爱扩展方法。在介绍 Context Receiver 之前,我们先了解一下 Kotlin 目前的扩展方法实现存在的问题。举个例子,在 Android 中,我们通常都会写一个扩展方法将 int 转换为 dp:funInt.dp(context:Context)...

Android技术之家 384

30. 如何使用 SEGW 的 redefine 功能对 SAP 标准 OData 服务进行扩展

SAP 发布了很多标准的 Fiori 应用,可以通过笔者的文章里提到的进行浏览。比如 My Opportunities 就是 SAP CRM 领域的一个 Fiori 应用:下图是 Opportunity 明细页面的 Info 标签页:假设我们想在这些标准字段的末尾增添一个新字段 Created By,用来维护当前 Opportunity 的创建者。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1175

Kotlin上下文接收者深入源码分析(80)

Kotlin上下文接收者(Context Receivers)是Kotlin 1.6版本引入的一项重要特性,它允许函数和类在定义时声明需要特定的上下文类型。这一特性提供了一种强大的方式来组织和传递依赖,使得代码更加简洁、类型安全且易于维护。 从本质上讲,上下文接收者是一种特殊的接收者类型,它不同于传统的扩展接收者或分发接收者。上下文接收者允许函数或类在调用或实例化时隐式地访问特定的上下文对象,而无需显式地将这些对象作为参数传递。

Android开发领域创作者 755

081、LLC谐振变换器的短路特性与保护

本文深入分析了LLC谐振变换器在短路工况下的特性与保护策略。作者通过实际案例指出,LLC在输出短路时会出现谐振电流失控、ZVS条件丧失等危险现象,导致MOS管炸毁。文章详细阐述了短路保护的三大难点:响应速度、频率与电流的耦合、重启策略,并提出了硬件级快速保护、频率快速拉升、打嗝模式等实用解决方案。作者分享了关键参数设计经验,包括电流检测阈值设置、最高频率限制和保护响应时间控制,并通过实测波形验证了保护效果。最后总结出五点重要经验,强调硬件保护的必要性和系统设计的注意事项,为LLC变换器的短路保护设计提供了有

Jack010169的博客 76

极智AI | 算能SC5+智算卡驱动安装与升级

大家好,我是极智视界,本文介绍一下 算能SC5+智算卡驱动安装与升级

极智视界 1277

android中gradle的kotlin编译配置选项

传递参数的发过誓可以直接在里面写。该配置在其余平台不可用。

Mr_Tony的专栏 2936

Vue3 - 详细实现移动端H5网页调用摄像头拍照功能,微信公众号网页h5页面打开本地摄像头并点击按钮照相,vue3获取摄像头画面执行拍照保存,调整摄像头前置后置抓拍图片给用户展示或保存手机上传服务器

vue3,H5网页,手机移动端,vue3摄像头拍照,前端h5调用手机摄像头,移动端网页调用摄像头,H5实现手机拍照和选择上传,调用本地摄像头实现拍照,vue3调用摄像头拍照,手机浏览器开启摄像头并进行拍照,H5移动端网页中调用手机摄像头,H5实现web调用摄像头在线照相,vue3h5调用摄像头,微信公众号网页如何调用摄像头拍照,公众号H5页面实现打开摄像头照相,怎么生成临时图片,vue3项目调用摄像头实现拍照功能,摄像头打不开没反应,浏览器不兼容怎么办,摄像头权限不弹出,只能在本地运行,拒绝权限后怎么办,

🏆 前端领域优质创作者 1万+

Kotlin版Gradle插件开发

Gradle 插件只不过是我们希望一起执行的任务的组合。

不学习傻了吧 9781

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

Kotlin中context和this的用法

在Kotlin开发中经常会用到context和this,本文列出一些常见的用法

GEBWM的博客 1754

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

android studio打包报错,多个版本kotlin冲突问题

android studio编译apk,多jar/aar引用多版本kotlin冲突报错

一颗橙 5656

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Kotlin1.6.20好用的新特性:多receiver扩展函数,绝不可空类型,并行编译kotlin减少编译时间

前言 最近kotlin1.6.20新版本出来了,并带来了几个很好用的特性,来尝尝鲜 正文 1.多receiver扩展函数 kt的扩展函数大家应该都懂,但什么是receiver呢?比如下面的代码,是给View扩展的,View就是这个receiver的类型,而this@click就是这个receiver对象 fun View.click(onClick: (View) -> Unit) { setOnClickListener { onClick(this@click)

lt的博客 3242

BroadcastReceiver 中的Context理解

先看一下Receiver的官方文档:关键在于如何理解,广播运行时的上下文环境。对于Receiver的注册,分为两种情况,第一种,在Manifest中注册,如下 <receiver android:name=".MyReceiver"> <intent-filter> <action android:name="com.zhty.t

小天的博客 5595

Spark Streaming-StreamingContext初始化与Receiver启动原理剖析与源码分析

一 StreamingContext初始化过程 StreamingContext是很多Streaming功能的入口,如:它提供从多种数据源创建DStream的方法等。在StreamingContext创建时将会创建如下主要组件: 1、创建DStreamGraph,并为其设置转换成RDD的时间间隔 private[streaming] val graph: DStreamGraph = { ...

上海一九四三 646

一定要用相同的Context 对同一个receiver进行registerReceiver与unregisterReceiver吗?

最近在开发一些功能,突然想到在动态注册和反注册receiver的时候一定要用相同的context吗?我不敢肯定咨询了同事,得到的答案是不行的,然而为了进一步佐证他的观点我自己尝试了一下。也就是查了一下相关代码。众所周知android的应用程序在ActivityThread的public static void main(String[] args)中开始调用我们写的代码,Application 、A

autumn_xl的专栏 4368

BroadcastReceiver中的Context

原文 What is the Context passed into onReceive() of a BroadcastReceiver? 按注册方式分: 静态注册 android.app.ReceiverRestrictedContext 动态注册 在哪里动态注册Context就是哪个Activity

有速度的空气的博客 2553

kotlin Context使用

kotlin语法里面用Android的上下文context的几种方式

二十不悔,三十而立 1万+

Android 项目升级 kotlin到1.6出现的问题

Android 项目升级 kotlin到1.6出现的问题前言解决方法完事 前言 由于最近将自己的库进行升级,把能和Android分离的库,都处理成跨平台库,或者jvm的库。在最新的IDEA中顺手就将kotlin升级到了1.6.10 ,万万没想到,灾难来了。由于jetpack compose 1.0.5版本还是依赖在kotlin1.5,jetpack compose代码就会报各种错误,如下: void org.jetbrains.kotlin.psi2ir.generators.TypeTranslatorI

杨迪龙的博客 7394

自定义Spark Streaming接收器(Receivers)

Spark Streaming除了可以使用内置的接收器(Receivers,比如Flume、Kafka、Kinesis、files和sockets等)来接收流数据,还可以自定义接收器来从任意的流中接收数据。开发者们可以自己实现org.apache.spark.streaming.receiver.Receiver类来从其他的数据源中接收数据。本文将介绍如何实现自定义接收器,并且在Spark S

u011007180的博客 704
上一篇: 聊聊 Kotlin 代理的“缺陷”与应对
下一篇: 【Android】用Android写个自己的画板APP
不秃头de程序猿
博客等级 码龄5年 4089粉丝 1478原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值