4. Kotlin注解基础:注解的定义、元注解、注解的使用目标与保留策略
注解这东西,说白了就是给代码贴标签。你写个类、写个方法,往上面加个注解,就等于告诉编译器或者运行时框架:「嘿,这个家伙有点特殊,你注意一下」。我在做Android开发的头两年,其实对注解的理解也就停留在@Override和@Nullable这个层面,直到后来开始做路由框架和编译期代码生成,才真正体会到注解的威力。
今天我们就来把Kotlin注解的基础彻底捋一遍。你想想看,如果连注解怎么定义、怎么控制它的生命周期都不清楚,后面学反射和注解处理器肯定会踩坑。
4.1 注解的定义:从Java到Kotlin的进化
在Kotlin里定义一个注解,用的是annotation class关键字。这跟Java的@interface有点像,但更简洁。
// Kotlin注解定义
annotation class BindView(val id: Int)
// 使用
class MainActivity {
@BindView(id = R.id.tv_title)
lateinit var titleView: TextView
}
我个人习惯把注解定义放在一个单独的包下,比如com.example.annotations。这样项目结构清晰,别人一看就知道这是自定义注解。
这里有个细节要注意:Kotlin注解的构造函数参数是有严格限制的。只能传这些类型:
- 基本类型(Int、Long、Boolean等)
- String
- 枚举
- 其他注解
- Class引用(
KClass) - 以上类型的数组
Object进去,编译直接报错。
4.2 元注解:注解的注解
元注解就是用来修饰注解的注解。听起来有点绕,但其实就是给注解本身打标签。Kotlin从Java继承了几种常用的元注解,也加了一些自己的东西。
| 元注解 | 作用 | 我的经验 |
|---|---|---|
| @Target | 指定注解可以用在什么地方 | 最常用,几乎每个自定义注解都要配 |
| @Retention | 指定注解保留到哪个阶段 | 编译期处理用SOURCE,运行时反射用RUNTIME |
| @Repeatable | 允许同一个地方重复使用该注解 | 用得少,但做权限校验时很香 |
| @MustBeDocumented | 让注解出现在API文档中 | 做SDK开发时建议加上 |
举个例子,我要定义一个只在编译期生效的注解:
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class DebugOnly
嗯,这里要注意AnnotationTarget和AnnotationRetention是Kotlin的枚举,跟Java的ElementType和RetentionPolicy对应。
4.3 使用目标:注解到底贴在哪?
Kotlin的@Target比Java丰富得多。Java只有类、方法、字段这些,Kotlin还细分到了属性、getter、setter、构造函数参数等等。
我曾经在做一个数据库ORM框架时,需要区分注解是加在Java字段上还是Kotlin属性上。如果不搞清楚这个,反射拿数据的时候就会拿不到。
// 常见的AnnotationTarget取值
@Target(
AnnotationTarget.CLASS, // 类、接口、对象
AnnotationTarget.FUNCTION, // 函数
AnnotationTarget.PROPERTY, // 属性
AnnotationTarget.FIELD, // 字段(Java中的field)
AnnotationTarget.VALUE_PARAMETER, // 构造函数参数
AnnotationTarget.EXPRESSION, // 表达式
AnnotationTarget.TYPE, // 类型
AnnotationTarget.TYPE_PARAMETER // 类型参数
)
@Target(AnnotationTarget.CLASS)最保险。等用到了再扩展。
4.4 保留策略:注解的生命周期
保留策略决定了注解能活多久。Kotlin的AnnotationRetention有三种:
- SOURCE:只在源码中存在,编译后就被丢弃。适合做代码检查、代码生成。
- BINARY:保留到字节码中,但运行时反射不可见。适合做字节码插桩。
- RUNTIME:保留到运行时,可以通过反射获取。适合做运行时处理。
我刚开始学的时候,总觉得RUNTIME最强大,什么注解都用它。后来发现,如果只是做编译期代码生成,用SOURCE就够了。用RUNTIME反而会增加APK体积,因为注解信息会被打包进去。
// 编译期处理,不保留到运行时
@Retention(AnnotationRetention.SOURCE)
annotation class GenerateLayout
// 运行时反射需要
@Retention(AnnotationRetention.RUNTIME)
annotation class NeedPermission
4.5 知识体系总览
下面这张图把Kotlin注解的核心知识点串起来了。你可以把它当作一个快速参考。
4.6 实战:写一个完整的自定义注解
光说不练假把式。我们结合前面讲的所有知识点,写一个完整的注解示例。
// 1. 定义注解
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class LogExecutionTime(
val tag: String = "Performance",
val level: Int = 1
)
// 2. 使用注解
class UserRepository {
@LogExecutionTime(tag = "DB_QUERY", level = 2)
fun fetchUsers(): List<User> {
// 模拟耗时操作
Thread.sleep(100)
return listOf()
}
}
// 3. 运行时通过反射处理
fun processLogAnnotations(obj: Any) {
val methods = obj::class.java.declaredMethods
for (method in methods) {
val annotation = method.getAnnotation(LogExecutionTime::class.java)
if (annotation != null) {
println("方法: ${method.name}, tag: ${annotation.tag}, level: ${annotation.level}")
}
}
}
核心要点回顾:
- 定义注解用
annotation class,参数类型有限制 - 元注解控制注解的行为:
@Target、@Retention、@Repeatable - 使用目标决定注解能贴在哪,保留策略决定注解活多久
- 编译期处理用
SOURCE,运行时反射用RUNTIME
好了,Kotlin注解的基础就这些。说白了就是三件事:怎么定义、贴在哪、活多久。把这些搞清楚了,后面学反射和注解处理器就会顺畅很多。