使用 Kotlin 的 Opt-in (选择加入)功能注解API提示当前非稳定API

news2024/11/25 23:02:41

前言

之前在给公司项目封装库的时候,领导告诉我封装的漂亮一点,等以后公司发展起来了可能需要把这个库提供给第三方接入使用。

此时,就有这么一个问题:某些功能函数使用条件比较苛刻,直接使用可能会出现意想不到的后果,如果想要使用,需要结合其他状态判断是否可以使用。

为了避免第三方接入时误操作,我为这个使用条件苛刻的函数另外封装了一个可以直接使用的新函数。

但是,即使如此,出于测试和维护需求,我也不能移除或者将原函数设置为私有(private)函数。

那么问题来了,我要怎么避免其他同事或者第三方在使用时不会误调用这个函数,同时又能在知晓直接使用可能导致的后果时依旧能够使用呢?

靠文档声明?显然这是不可靠的,就算你在文档中大写加粗标红强调这类函数的危险性,依旧会有人视而不见。

当时我在谷歌苦苦搜寻了好久,终于在 Kotlin 官方文档中找到一个完美契合我的需求的功能,那就是 Opt-in 。

当时关于 Opt-in 的资料,除了官方文档几乎没有其他资料,我也没有在实际中见到有什么库或者程序使用这个功能,所以我用起来还是觉得心里发怵。

直到今年我开始接触了 Compose ,我才发现,原来 Compose 中已经大量应用了这个功能:

s1.png

s2.png

并且在今年的年中发布的 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 中 LazyColunmstickyHeader 函数举例,我们不需要关心这个函数的具体实现,只需要知道这个函数被标记为了 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 错误:

s3.png

如果我们无视这个警告,直接编译的话也会编译出错。

为了消除这个警告,我们可以选择加上注解: @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)

s4.png

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

s5.png

最后,可能有人想问,我不想到处都写上 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.ERRORRequiresOptIn.Level.WARNING

ERROR 表示被注解的地方强制启用选择加入,如果不声明选择加入,则编译将不通过:

@NotSafeForUse
fun testFun() {

}

@RequiresOptIn("直接使用该方法可能会导致意想不到的错误,请确认已知晓该方法可能会产生的问题后再使用", 
annotation class NotSafeForUse

以上代码在IDE会被标注红色下划线警告,并且编译时将报错:

s6.png

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

s7.png

同样的,普通注解可以使用的配置参数,选择加入也可以使用:

@RequiresOptIn("直接使用该方法可能会导致意想不到的错误,请确认已知晓该方法可能会产生的问题后再使用", RequiresOptIn.Level.ERROR)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class NotSafeForUse

需要注意的是,@Retention 需要为 BINARYRUNTIME

另外,对于选择加入的注解,还需要满足以下条件:

  • 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 会警告:

s8.png

参考资料

  1. Opt-in requirements
  2. What’s new in Kotlin 1.7.0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/574288.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Mock.js 的语法规范学习

Mock.js 有一套完整的语法规范,可以好好学学。 Mock.js 的语法规范包括两部分&#xff1a; 数据模板定义规范&#xff08;Data Template Definition&#xff0c;DTD&#xff09; 数据占位符定义规范&#xff08;Data Placeholder Definition&#xff0c;DPD&#xff09; 数…

【mediasoup】12: ChannelRequest控制指令

rust 是把worker 当做lib 调用的。node是当做一个进程每一个ChannelRequest 就是一个外部发给worker的控制指令worker要负责处理。控制指令的处理实际是worker做的,worker可能立即执行,可能交给对应的handler去处理 worker根据指令id 来处理 处理完毕后才发消息ack 给控制侧 …

# Spring Boot 中如何使用 Spring Cloud Sleuth 来实现分布式跟踪?

Spring Boot 中如何使用 Spring Cloud Sleuth 来实现分布式跟踪&#xff1f; 在微服务架构中&#xff0c;通常会有多个服务相互协作&#xff0c;为了方便排查问题&#xff0c;我们需要对服务之间的调用进行跟踪。Spring Cloud Sleuth 是 Spring Cloud 生态中的分布式跟踪解决方…

charles使用

charles​ 一、概念​ charles是一款非常优秀的抓包工具&#xff0c;全平台支持&#xff0c;在mac&#xff0c;windows&#xff0c;linux上都可以使用&#xff0c;既可以抓 取web端的包&#xff0c;也可以抓app端的包。 ​ charles主要的功能包括如下几点&#xff1a; ​ 截取…

Linux网络服务:SSH远程访问及控制2

目录 一、理论 1.构建密钥对验证的SSH体系 2.TCP Wrappers访问控制 二、实验 1.ecdsa免密连接 2.rsa免密连接 一、理论 1.构建密钥对验证的SSH体系 &#xff08;1&#xff09;免密连接原理 ① 手动添加客户端的公钥到服务端 ② 服务端收到客户端的公钥后使用客户端公钥…

C++——引用

引用的概念 初步理解&#xff1a;引用相当于给变量取了一个别名&#xff0c;它和引用的变量共用同一块空间。 就好比孙悟空有很多外号&#xff0c;例如孙行者&#xff0c;齐天大圣&#xff0c;斗战胜佛&#xff0c;但是它们所指都是孙悟空。同样的&#xff0c;如果齐天大圣大…

如何在 Ubuntu 22.04 上安装 Python Pip?

Python Pip 是 Python 的包管理器&#xff0c;它允许您轻松地安装和管理 Python 包和库。在 Ubuntu 22.04 上安装 Python Pip 是非常简单的。 本文将详细介绍如何在 Ubuntu 22.04 上安装 Python Pip&#xff0c;并为您提供逐步指南。 步骤 1&#xff1a;更新软件包列表 在安装…

C Primer Plus第八章编程练习答案

学完C语言之后&#xff0c;我就去阅读《C Primer Plus》这本经典的C语言书籍&#xff0c;对每一章的编程练习题都做了相关的解答&#xff0c;仅仅代表着我个人的解答思路&#xff0c;如有错误&#xff0c;请各位大佬帮忙点出&#xff01; 1.设计一个程序&#xff0c;统计在读到…

Yum使用方法

1.什么是软件包 在Linux下安装软件&#xff0c;有三种方法&#xff1a; 通过对源代码进行封装&#xff0c;并进行编译&#xff0c;得到可执行程序。rpm安装&#xff0c;rpm安装软件需要各种指令&#xff0c;对于小白来说不友好&#xff0c;容易出错。yum安装&#xff0c;解决…

六、Docker仓库之Harbor搭建(三)

Harbor搭建 一、Harbor简介 1.Harbor介绍 Harbor是一个用于存储Docker镜像的企业级镜像服务器&#xff0c;通过添加一些企业必需的功能特性&#xff0c;如安全、标识和管理等&#xff0c;大大扩展其功能。作为一个企业级私有镜像服务器&#xff0c;Harbor提供了更好的性能和安…

Modern CSV:大型 CSV 文件编辑器/查看器 Crack

Modern CSV用于快速查看大型 CSV 文件 适用于 Windows、Mac 和 Linux 的复杂 CSV 编辑器/查看器 被使用 电子商务运营商。数据科学家。会计师。 IT 专业人员。学生。医学研究人员。数字营销人员。生物学家。工程师。 现代 CSV 是适用于 Windows、Mac 和 Linux 的功能强大的表格…

SSM 如何使用 TCC 机制实现分布式事务?

SSM 如何使用 TCC 机制实现分布式事务&#xff1f; 分布式事务是现代分布式系统中必不可少的一部分&#xff0c;而 TCC 机制&#xff08;Try-Confirm-Cancel&#xff09;是一种常用的分布式事务处理方式。在 SSM 框架中&#xff0c;我们可以使用 TCC 机制来管理分布式事务。本…

RabbitMQ系列-概念及安装

1. 消息队列 消息队列是指利用队列这种数据结构进行消息发送、缓存、接收&#xff0c;使得进程间能相互通信&#xff0c;是点对点的通信 而消息代理是对消息队列的扩展&#xff0c;支持对消息的路由&#xff0c;是发布-订阅模式的通信&#xff0c;消息的发送者并不清楚消息的…

Spring源码解析

Idea导入Spring源码 下载 下载gradle 因为Spring源码里没有使用Maven依赖&#xff0c;而是使用gradle依赖&#xff0c;所以我们需要在本地下载安装并配置gradle环境。注意&#xff0c;这里下载安装的gradle版本应与Spring源码中的gradle版本对应。这里推荐下载我的&#xff…

Linux网络服务:部署YUM仓库与NFS服务

目录 一、理论 1.部署YUM仓库服务 2.NFS共享存储服务 二、实验 1.通过httpd服务建立yum仓库 2.通过vsftpd服务建立yum仓库 3.搭建NFS实现2台或3台服务器共享一个目录 一、理论 1.部署YUM仓库服务 (1) YUM简介 YUM的前身是YUP&#xff0c;借助于YUM软件仓库&#xff0c…

关于原型链

1-__proto__([[Prototype]])和prototype 每个对象都有一个隐式原型&#xff0c;这个隐式原型可以通过 obj.__proto__ Object.getPrototypeOf(obj)这两种方式获取&#xff1b; 我们都知道对象是通过构造函数构造的&#xff0c;new关键字构造的&#xff0c; 构造函数上有一个显…

如何在华为OD机试中获得满分?Java实现【最佳对手】一文详解!

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Java华为OD机试真题(2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述4. Java算法源码5. 测试6.解题思路1. 题目描述 游戏里面,队伍通过匹配…

native层函数没有导出时,如何获得相应函数地址?

前言 每次App重新运行后native函数加载的绝对地址是会变化的&#xff0c;唯一不变的是函数相对于基地址的偏移&#xff0c;因此我们可以在获取模块的基地址后加上固定的偏移地址获取相应函数的地址&#xff0c;Frida中也正好提供了这种方式&#xff1a;先通过Module.findBaseA…

SQL教程1

SQL 是用于访问和处理数据库的标准的计算机语言。 在本教程中&#xff0c;您将学到如何使用 SQL 访问和处理数据系统中的数据&#xff0c;这类数据库包括&#xff1a;MySQL、SQL Server、Access、Oracle、Sybase、DB2 等等。 SQL 简介 SQL (Structured Query Language:结构化…

OSPF协议

OSPF&#xff1a;开放式最短路径优先协议 无类别IGP协议&#xff1b;链路状态型&#xff1b;基于LSA收敛&#xff0c;故更新量较大&#xff0c;为在中大型网络正常工作&#xff0c;需要进行结构化的部署 --- 区域划分、ip地址规划 组播更新 --- 224.0.0.5 224.0.0.6 支持等开销…