使用 Kotlin 的 Opt-in (选择加入)功能注解API提示当前非稳定API
前言
之前在给公司项目封装库的时候,领导告诉我封装的漂亮一点,等以后公司发展起来了可能需要把这个库提供给第三方接入使用。
此时,就有这么一个问题:某些功能函数使用条件比较苛刻,直接使用可能会出现意想不到的后果,如果想要使用,需要结合其他状态判断是否可以使用。
为了避免第三方接入时误操作,我为这个使用条件苛刻的函数另外封装了一个可以直接使用的新函数。
但是,即使如此,出于测试和维护需求,我也不能移除或者将原函数设置为私有(private)函数。
那么问题来了,我要怎么避免其他同事或者第三方在使用时不会误调用这个函数,同时又能在知晓直接使用可能导致的后果时依旧能够使用呢?
靠文档声明?显然这是不可靠的,就算你在文档中大写加粗标红强调这类函数的危险性,依旧会有人视而不见。
当时我在谷歌苦苦搜寻了好久,终于在 Kotlin 官方文档中找到一个完美契合我的需求的功能,那就是 Opt-in 。
当时关于 Opt-in 的资料,除了官方文档几乎没有其他资料,我也没有在实际中见到有什么库或者程序使用这个功能,所以我用起来还是觉得心里发怵。
直到今年我开始接触了 Compose ,我才发现,原来 Compose 中已经大量应用了这个功能:


并且在今年的年中发布的 Kotlin 1.7.0 中,该功能终于发布了正式版本。
所以,是时候介绍一下这个功能了。
正文
什么是 Opt-in
根据官方文档介绍。
Opt-in 是 Kotlin 标准库中的一个方法,用于声明使用某些 API 需要明确的同意。该功能可以让开发者告知 API 使用者使用某些 API 需要一些特定的条件,如果使用者已经知晓则需要明确声明依旧使用(Opt-in)才能继续使用该 API。
例如,某些 API 尚处于测试阶段,未来可能会发生变化;亦或是我前言中提到的场景,都非常适合使用该方法。
如果我们声明了某个方法(functiuon)或类(class)需要 Opt-in ,则IDE或编译器会发出警告,要求使用者明确标注需要使用(Opt-in)。
如何使用
在介绍怎么编写 Opt-in 注解之前,我们先简单介绍一下如何使用。
这里我们就以 Compose 中 LazyColunm 的 stickyHeader 函数举例,我们不需要关心这个函数的具体实现,只需要知道这个函数被标记为了 Opt-in :
@ExperimentalFoundationApi
fun stickyHeader(key: Any? = null,contentType: Any? = null,content: @Composable LazyItemScope.() -> Unit
)@RequiresOptIn("This foundation API is experimental and is likely to change or be removed in the " +"future."
)
annotation class ExperimentalFoundationApi
其中 ExperimentalFoundationApi 即用于标记需要选择加入的注解名称。
可以看到, stickyHeader 被加上了 @ExperimentalFoundationApi 的注解。
此时,如果我们直接调用 stickyHeader ,将会收到如下的 IDE 错误:

如果我们无视这个警告,直接编译的话也会编译出错。
为了消除这个警告,我们可以选择加上注解: @OptIn(ExperimentalFoundationApi::class) 表示我们已知晓使用 stickyHeader 的风险,并且依旧需要使用。
加上上述注解后,错误消失,也可以正常编译运行了。
@OptIn 的作用域可以是方法(函数)、类、文件:
// 1. 注解方法
@OptIn(ExperimentalFoundationApi::class)
fun Test() {}// 2. 注解类
@OptIn(ExperimentalFoundationApi::class)
class Test {}// 3. 注解整个文件
@file:OptIn(ExperimentalFoundationApi::class)
分别对应这个方法选择加入、这个类中的所有方法都选择加入、整个文件中的所有方法,函数,类都加入。
需要注意的是,直接使用 OptIn(ExperimentalFoundationApi::class) 表示的是不传递选择加入,即如果我们在 Test() 函数中注解了 OptIn(ExperimentalFoundationApi::class) ,则调用 Test() 的地方不需要再声明 OptIn(xxx):

如果我们想要让选择加入传递下去,则可以更改 Test() 的注解为 @ExperimentalFoundationApi,此时调用了 Test() 的地方也需要声明选择加入:

最后,可能有人想问,我不想到处都写上 OptIn(xxxx) 怎么办?就算可以注解给整个文件我也觉得很麻烦啊。
那么,你可以选择直接给整个模块(Moudle)都加上注解,我们需要给当前模块的编译配置加上以下编译选项:
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {kotlinOptions {freeCompilerArgs += "-opt-in=org.mylibrary.OptInAnnotation"}
}
需要注意的是对于 Kotlin 1.6.0 之前的版本,请将 -opt-in 替换为 -Xopt-in 。
另外,如果使用的是 kts,则为:
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {kotlinOptions.freeCompilerArgs += "-opt-in=org.mylibrary.OptInAnnotation"
}
如何自己编写
上面我们简单讲解了如何使用 Opt-in 。大家现在应该对 Opt-in 有了一个大致的理解,所以接下来我们讲解如何自己写一个 Opt-in 注解。
创建选择加入注解和创建普通注解一样,只是多了一个额外的配置选项:
@RequiresOptIn("直接使用该方法可能会导致意想不到的错误,请确认已知晓该方法可能会产生的问题后再使用", RequiresOptIn.Level.ERROR)
annotation class NotSafeForUse
在上面的代码中我们创建了一个名为 NotSafeForUse 的注解。
并且为 NotSafeForUse 添加了一个 @RequiresOptIn 注解,该注解用于声明 NotSafeForUse 是一个选择加入的注解。
@RequiresOptIn 接收两个参数:
message即使用时的提示文本level警告级别
警告级别可选择 RequiresOptIn.Level.ERROR 或 RequiresOptIn.Level.WARNING 。
ERROR 表示被注解的地方强制启用选择加入,如果不声明选择加入,则编译将不通过:
@NotSafeForUse
fun testFun() {}@RequiresOptIn("直接使用该方法可能会导致意想不到的错误,请确认已知晓该方法可能会产生的问题后再使用",
annotation class NotSafeForUse
以上代码在IDE会被标注红色下划线警告,并且编译时将报错:

如果把级别改为 WARNING 则仅警告而不会导致编译失败,同时 IDE 也只会提示弱化的警告:

同样的,普通注解可以使用的配置参数,选择加入也可以使用:
@RequiresOptIn("直接使用该方法可能会导致意想不到的错误,请确认已知晓该方法可能会产生的问题后再使用", RequiresOptIn.Level.ERROR)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class NotSafeForUse
需要注意的是,@Retention 需要为 BINARY 或 RUNTIME
另外,对于选择加入的注解,还需要满足以下条件:
- No EXPRESSION, FILE, TYPE, or TYPE_PARAMETER among targets
- No parameters.
其他问题
在 Kotlin 1.7.0 之前,Opt-in 自身也处于 Opt-in 状态,所以如果我们的 Kotlin 版本在 1.7.0 之前,想要使用 Opt-in 必须先声明 Opt-in Opt-in(绕口令呢?)
为了声明使用选择加入,我们需要添加编译配置:
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
}
对于 kts 则使用:
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
}
如果不添加这个编译选项的话。直接使用 Opt-in 会警告:

参考资料
- Opt-in requirements
- What’s new in Kotlin 1.7.0
相关文章:
使用 Kotlin 的 Opt-in (选择加入)功能注解API提示当前非稳定API
前言 之前在给公司项目封装库的时候,领导告诉我封装的漂亮一点,等以后公司发展起来了可能需要把这个库提供给第三方接入使用。 此时,就有这么一个问题:某些功能函数使用条件比较苛刻,直接使用可能会出现意想不到的后…...
webpack配置排除打包
webpack配置排除打包 思路 打包时,不要把类似于element-ui第三方的这些包打进来 从网络上,通过url地址直接引入这些包 操作 (1)先找到 vue.config.js, 添加 externals 项,具体如下: config…...
HNU-操作系统OS-ucoreLab系列-感悟
谨以此片篇,献给熬夜的8个晚上,以及逝去的时光。 感悟: 今天结束了所有的Lab实验(2023.6.3),感慨万千。 喜是这个实验终于结束了,悲是其实有好多地方我都没有理解。 应该指出,由于验收的助教学长学姐们的宽容,HNU实际上在验收这一块的要求还是比较低的。 但是这个…...
MySQL运维篇(三)
五.读写分离 5.1 介绍 读写分离,简单地说是把对数据库的读和写操作分开,以对应不同的数据库服务器。主数据库提供写操作,从数据库提供读操作,这样能有效地减轻单台数据库的压力。 通过MyCat即可轻易实现上述功能,不仅可以支持MySQL&#x…...
Lecture 2 Text Preprocessing
目录 Some DefinitionsReasons for PreprocessingPreprocessing StepsSentence Segmentation 句子分割Binary Classifier 二元分类器Word Tokenization: English 英文词元标记化Word Tokenization: Chinese 中文词元标记化Word Tokenization: German 德语词元标记化Subword Tok…...
web练习第二周
前言:(博主个人学习笔记,不用看)web练习第二周,仅做出前3题。相比于第一周,难度大幅增加,写题时就算看了wp还是像个无头苍蝇一样到处乱创,大多都是陌生知识点,工具的使用…...
LC-1439. 有序矩阵中的第 k 个最小数组和(二分答案、多路归并)
1439. 有序矩阵中的第 k 个最小数组和 难度困难120 给你一个 m * n 的矩阵 mat,以及一个整数 k ,矩阵中的每一行都以非递减的顺序排列。 你可以从每一行中选出 1 个元素形成一个数组。返回所有可能数组中的第 k 个 最小 数组和。 示例 1:…...
一文1000字从0到1实现Jenkins+Allure+Pytest的持续集成
一、配置 allure 环境变量 1、下载 allure是一个命令行工具,可以去 github 下载最新版:https://github.com/allure-framework/allure2/releases 2、解压到本地 3、配置环境变量 复制路径如:F:\allure-2.13.7\bin 环境变量、Path、添加 F:\…...
给一个有序数组生成平衡搜索二叉树(java)
给一个有序数组生成平衡搜索二叉树 给一个有序数组生成平衡搜索二叉树递归生成二叉树专题 给一个有序数组生成平衡搜索二叉树 给定一个有序的数组,用这个数组生成一个平衡搜索二叉树. 这个题还是很简单的,知道什么时平衡搜索二叉树就行了, 左边值小于头节点值,头节点值小于右边…...
【JavaSE】Java基础语法(二十二):包装类
文章目录 1. 基本类型包装类2. Integer类3. 自动拆箱和自动装箱4. int和String类型的相互转换 1. 基本类型包装类 基本类型包装类的作用 将基本数据类型封装成对象的好处在于可以在对象中定义更多的功能方法操作该数据常用的操作之一:用于基本数据类型与字符串之间的…...
javascript基础十八:说说你对JavaScript中事件循环的理解
一、是什么 JavaScript 在设计之初便是单线程,即指程序运行时,只有一个线程存在,同一时间只能做一件事 为什么要这么设计,跟JavaScript的应用场景有关 JavaScript 初期作为一门浏览器脚本语言,通常用于操作 DOM &#…...
详解js中的浅拷贝与深拷贝
详解js中的浅拷贝与深拷贝 1、前言1.1 栈(stack)和堆(heap)1.2 基本数据类型和引用数据类型1.2.1 概念1.2.2 区别1.2.3 基本类型赋值方式1.2.4 引用类型赋值方式 2、浅拷贝2.1 概念2.2 常见的浅拷贝方法2.2.1 Object.assign()2.2.…...
Day9 敏捷测试——敏捷开发的特征、什么是敏捷测试?、极限编程、极限测试
Day9 敏捷测试——敏捷开发的特征、什么是敏捷测试?、极限编程、极限测试 文章目录 Day9 敏捷测试——敏捷开发的特征、什么是敏捷测试?、极限编程、极限测试敏捷开发的特征1、迭代式开发2、增量交付3、及时反馈4、持续集成5、自我管理敏捷开发和迭代式开发的根本区别1、性质…...
k8s 维护node与驱逐pod
1.维护node节点 设置节点状态为不可调度状态,执行以下命令后,节点状态会多出一个SchedulingDisabled的状态,即新建的pod不会往该节点上调度,本身存在node中的pod保持正常运行 kubectl cordon k8s-node01 kubectl get node 2.驱…...
SouapUI接口测试之创建性能测试
SouapUI也是一个能生动的体现一个系统(项目)性能状态的工具,本篇就来说说如何在SouapUI工具下创建性能测试 一、创建测试用例 由于在《SouapUI接口测试之使用Excel进行参数化》篇已经创建好了测试用例,本篇就不讲解如何创建测试…...
springboot整合kafka入门
kafka基本概念 producer: 生产者,负责发布消息到kafka cluster(kafka集群)中。生产者可以是web前端产生的page view,或者是服务器日志,系统CPU、memory等。 consumer: 消费者,每个consumer属于一个特定的c…...
Rust 笔记:Rust 语言中的字符串
Rust 笔记 Rust 语言中的字符串 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm1001.2101.3001.5343 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_28550263/article/detail…...
华为OD机试真题 Java 实现【将真分数分解为埃及分数】【牛客练习题】
一、题目描述 分子为1的分数称为埃及分数。现输入一个真分数(分子比分母小的分数,叫做真分数),请将该分数分解为埃及分数。如:8/11 = 1/2+1/5+1/55+1/110。 注:真分数指分子小于分母的分数,分子和分母有可能gcd不为1! 如有多个解,请输出任意一个。 二、输入描述 输…...
Zemax Lumerical | 二维光栅出瞳扩展系统优化
简介 本文提出并演示了一种以二维光栅耦出的光瞳扩展(EPE)系统优化和公差分析的仿真方法。 在这个工作流程中,我们将使用3个软件进行不同的工作 ,以实现优化系统的大目标。首先,我们使用 Lumerical 构建光栅模型并使用…...
Linux-0.11 文件系统read_write.c详解
Linux-0.11 文件系统read_write.c详解 模块简介 该模块实现了文件系统通用的读写的方法read/write/lseek。 根据文件类型的不同,在内部将调用不同的方法。如果是管道文件,则调用pipe.c中的读写方法,如果是字符设备,则会调用cha…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...
跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
