1. 引言

Kotlin 标准库提供了一种名为 Opt-in(选择加入) 的机制,用于对某些 API 元素的使用施加显式授权要求。这种机制让库开发者可以明确告知使用者:某个 API 存在特殊使用条件——比如它仍处于实验阶段,未来可能会发生不兼容变更。

通过这一机制,调用方必须“主动同意”才能使用相关 API,从而避免误用导致后期升级困难。✅
这在开发公共库时尤为重要,但在多模块项目中控制内部 API 使用也同样适用。

本文将深入讲解 Kotlin 的 Opt-in 机制,包括如何定义、标记和使用这类受控 API。

⚠️ 注意:在 Kotlin 1.7 之前,@RequiresOptIn 注解本身是实验性的,会触发编译警告。若要在旧版本中无警告地使用该功能,需手动配置编译器参数:

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
        freeCompilerArgs += [
            '-Xopt-in=kotlin.RequiresOptIn',
        ]
    }
}

从 Kotlin 1.7 起,此注解已稳定,无需额外配置。

2. Opt-in 机制概述

Opt-in 是一种基于注解的声明机制,允许我们标记某些 API 元素为“需授权使用”。被 @RequiresOptIn 标记的类、函数等,在未获得明确许可前,调用时会产生警告或错误。

核心作用

  • 防止用户无意中使用不稳定或高风险 API
  • 明确责任边界:调用者需知情并主动承担后果

典型应用场景:

  • 实验性 API(Experimental APIs)
  • 内部 API(Internal APIs)
  • 高权限操作(如反射、底层系统访问)

一旦某个元素被标记为需要 Opt-in,任何直接或间接使用它的代码都必须做出响应——要么显式授权(@OptIn),要么继续传递警告。

3. 创建 Opt-in API

要创建一个需要 Opt-in 才能使用的 API,分为两步:

  1. 定义一个 Opt-in 要求注解
  2. 使用该注解标记目标 API

3.1. 定义 Opt-in 要求注解

只需创建一个被 @RequiresOptIn 修饰的注解类即可:

@RequiresOptIn
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class ExperimentalClassApi

✅ 注解要求

该自定义注解必须满足以下条件:

  • 保留策略:必须是 BINARYRUNTIME
  • 目标位置:不能用于 EXPRESSIONFILETYPETYPE_PARAMETER
  • 无参数:不允许带构造参数

⚙️ 可选配置项

配置项 说明
level 控制违规行为的严重程度:
RequiresOptIn.Level.ERROR → 编译报错 ❌
RequiresOptIn.Level.WARNING → 仅警告 ⚠️(默认)
message 用户未授权时显示的提示信息

📌 示例:定义一个实验性接口注解,带友好提示:

@RequiresOptIn(
    level = RequiresOptIn.Level.WARNING,
    message = "This is an experimental API. It may be changed or removed in the future."
)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class ExperimentalClassApi

这样当别人调用被标记的方法时,IDE 会弹出黄色警告,提醒这是实验性功能。

Warning message from using an opt-in annotation class with a warning severity level.

3.2. 标记 API 为需 Opt-in

定义好注解后,就可以用来标记具体的 API。

例如,给一个实验性函数打上标签:

@ExperimentalClassApi
fun someExperimentalApi(): Unit {
}

此时,任何调用该函数的地方都会收到警告(或错误,取决于 level 设置)。

你也可以将注解加在类上,表示整个类都是实验性的:

@ExperimentalClassApi
class ExperimentalService {
    fun doWork() { ... }
}

只要使用了这个类(哪怕只是读字段、调方法),就会触发 Opt-in 提示。

4. 使用 Opt-in API 的几种方式

面对 Opt-in API 的警告或错误,有三种处理策略,适用于不同场景。

4.1. 传播 Opt-in 要求(Propagate)

有时你不希望立即“消化”这个警告,而是想把它传下去,让下游使用者自行决定是否接受。

✅ 场景举例:你在封装一个实验性库,不想自己担责,那就把风险一起暴露出去。

@ExperimentalClassApi
fun anotherExperimentalApi() {
    someExperimentalApi() // OK:同属 Experimental,无需额外 OptIn
}

⚠️ 效果:

  • anotherExperimentalApi() 内部可安全调用 someExperimentalApi()
  • 但所有调用 anotherExperimentalApi() 的地方,依然要处理 @ExperimentalClassApi

💡 这是一种“传染性”设计,确保依赖链上的每一环都清楚自己用了实验性功能。

📌 建议:库作者应广泛使用此模式,防止隐藏实验性依赖;而应用层应尽量避免,减少冗余注解。

4.2. 模块级 Opt-in(全局启用)

如果你确定整个模块都要使用某个实验性 API,可以通过编译参数一次性开启:

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
        freeCompilerArgs += [
            '-Xopt-in=com.example.lib.core.ExperimentalClassApi',
        ]
    }
}

✅ 优点:

  • 全模块统一管理,免去到处加 @OptIn

❌ 风险:

  • 容易滥用,导致大量无意识使用实验性 API
  • 后续迁移成本高

📌 推荐仅在测试模块或内部工具模块中使用,生产代码慎用。

4.3. 局部 Opt-in(精准授权)

最常见也最推荐的方式:在具体使用点上添加 @OptIn 注解。

单条语句级别

@OptIn(ExperimentalClassApi::class)
var i = someExperimentalApi()

方法级别

@OptIn(ExperimentalClassApi::class)
fun useExperimentalFeature() {
    someExperimentalApi()
    // ...
}

文件级别

若一个文件内多次使用,可在文件顶部统一声明:

@file:OptIn(ExperimentalClassApi::class)
package com.baeldung.opt.requirement

class OptInRequirementsDemo {
    @ExperimentalClassApi
    fun someExperimentalApi(): Unit {
    }

    fun anotherExperimentalApi() {
        someExperimentalApi() // OK:文件级 OptIn 已覆盖
    }

    var i = someExperimentalApi()
}

📌 小技巧:配合 IDE 快捷操作,快速生成 @OptIn,提升编码效率。

5. 总结

Opt-in 机制是 Kotlin 提供的一种强大且严谨的 API 控制手段,尤其适合构建高质量库或管理复杂项目中的依赖关系。

关键要点回顾:

方式 适用场景 推荐度
传播要求 库开发、中间层封装 ✅✅✅
模块级启用 测试模块、脚本工具 ✅⚠️(谨慎)
局部 Opt-in 应用层、精确控制 ✅✅✅✅

合理运用这些策略,不仅能规避潜在风险,还能提升团队协作中的透明度与责任感。

💡 踩坑提醒:不要为了省事在主模块全局开启 -Xopt-in,否则等于关闭了安全阀,后期升级极易翻车!

文中所有示例代码均可在 GitHub 获取:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-annotations


原始标题:Opt-in Requirements in Kotlin

« 上一篇: 在 Kotlin 中反转 Map
» 下一篇: Kotlin 中的数组修改