使用 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…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...

排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

LLMs 系列实操科普(1)
写在前面: 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容,原视频时长 ~130 分钟,以实操演示主流的一些 LLMs 的使用,由于涉及到实操,实际上并不适合以文字整理,但还是决定尽量整理一份笔…...

Java数组Arrays操作全攻略
Arrays类的概述 Java中的Arrays类位于java.util包中,提供了一系列静态方法用于操作数组(如排序、搜索、填充、比较等)。这些方法适用于基本类型数组和对象数组。 常用成员方法及代码示例 排序(sort) 对数组进行升序…...