- 声明注解
- 案例:使用元注解
- 注解目标声明
- 案例:读取运行时注解信息
与基本注解不同,在一般的应用开发中不会直接使用元注解,在开发框架或生成工具时会用到一些自定义注解,元注解是自定义注解的组成要素。
一、声明注解
声明自定义注解可以使用 annotation
关键字实现,最简单形式的注解实例代码如下:
annotation class Marker
上述代码声明一个 Marker
注解,annotation
声明一个注解类型,注解的可见性有 公有的、内部的 和 私有的,不能是保护的。
Marker
注解中不包含任何的成员,这种注解称为标记注解,标记注解属于基本注解。注解也可以有成员属性,通过构造函数初始化成员属性。如下:
annotation class MyAnnotation1(val value: String)
代码中 (val value: String)
是声明注解 MyAnnotation1
的构造函数,构造函数参数类型只能是 基本数据类型、字符串、类类型(KClass)、枚举、数组 和 其他的注解类型。
此外,构造函数 (val value: String)
同时声明了注解 MyAnnotation1
有一个成员属性 value
,成员属性的可见性只能是公有的。
注解成员属性也可以有默认值,如下:
annotation class MyAnnotation2(val value: String = "注解信息", val count: Int = 20)
使用这些注解示例代码如下:
@Marker // 1
class Person {
// 名字
@MyAnnotation1(value = "名字") // 2
@JvmField
var name = "Tony"
// 年龄
@MyAnnotation2(value = "年龄") // 3
var age = 18
}
@Marker // 4
fun main(args: Array) {
@Marker // 5
@MyAnnotation2(value = "实例化Person", count = 1) // 6
val p = Person()
}
默认情况下注解可以修饰任意的代码元素(类、接口、属性、变量、函数 和 数据类型等)。代码第1行、第4行和第5行都使用 @Marker
注解,分别修饰 类、函数 和 变量。
代码第2行是使用 @MyAnnotation1(value="名字")
注解修饰 name
成员属性,其中 value = "名字"
是为 value
属性提供数值。代码第3行是使用 MyAnnotation2(value = "年龄")
注解修饰成员函数,@MyAnnotation2
有两个成员属性,但是只为 value
成员属性赋值,另一个成员属性 count
是使用默认值。代码第6行使用 @MyAnnotation2(value = "实例化Person", count = 1)
注解修饰变量。
二、案例:使用元注解
上面声明的注解只是最基本形式的注解,对于复杂的注解可以在声明注解时使用元注解。下面在一个自定义注解中使用元注解。在此案例中定义了两个注解。
- 第一个注解,用来注解类和接口
@MustBeDocumented // 1
@Target(AnnotationTarget.CLASS) // 2
@Retention(AnnotationRetention.RUNTIME) // 3
annotation class MyAnnotation(val description: String) // 4
代码第4行声明注解类型 MyAnnotation
,其中使用了三个元注解修饰 MyAnnotaion
注解。代码第1行使用 @MustBeDocumented
指定 MyAnnotaion
注解信息可以被文档生成工具读取。代码第2行使用 @Target(AnnotationTarget.CLASS)
指定 MyAnnotaion
注解用于修饰类 和 接口等类型。代码第3行 @Retention(AnnotationRetention.RUNTIME)
指定 MyAnnotaion
注解信息可以在运行时被读取。代码第4行的 description
是 MyAnnotaion
注解的属性。
- 第二个注解,用来注解类中成员属性和函数
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME) // 1
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
) // 2
annotation class MemberAnnotation(val type: KClass<*> = Unit::class, val description: String) // 3
上述代码第3行是声明注解类型 MemberAnnotation
,其中也使用三个元注解修饰 MemberAnnotation
注解。代码第1行的 @Retention(AnnotationRetention.RUNTIME)
指定 MemberAnnotation
注解信息可以在运行时被读取。代码第2行 @Target
指定 MemberAnnotation
注解用于修饰类中成员(函数、属性、属性的getter访问器 和 属性的setter访问器)。
代码第3行中还声明 MemberAnnotation
注解的属性是 type
和 description
,type
类型是 KClass<*>
,默认值是 Unit::class
。description
类型是 String
,没有设置默认值。
提示:代码第3行的 KClass<*>
类型表示 KClass 的泛型,*
是泛型通配符,可以是任何类型。多数情况下泛型尖括号中指定的都是某个具体类型,泛型也是为此而设计的。但是有时确实不需要知道具体类型,或者说什么类型都可以,此时可以使用 *
通配所有类型。
- 使用
MyAnnotation
和MemberAnnotation
注解的Student
类
@MyAnnotation(description = "这是一个测试类") // 1
class Student {
@MemberAnnotation(type = String::class, description = "名字") // 2
@get:MemberAnnotation(type = String::class, description = "获得名字") // 3
var name: String? = null
private set
@MemberAnnotation(type = Int::class, description = "年龄") // 4
@get:MemberAnnotation(type = Int::class, description = "获得年龄") // 5
var age: Int = 0
private set
@MemberAnnotation(description = "设置姓名和年龄") // 6
fun setNameAndAge(name: String, age: Int) {
this.name = name
this.age = age
}
override fun toString(): String {
return "Person [name=$name, age=$age]"
}
}
如果当前类与注解不在同一个包中,则需要将注解引入。代码第1行的 @MyAnnotation
只能在 Student
类声明之前。代码第2行 @MemberAnnotation
注解 name
成员属性,代码第3行 @get:MemberAnnotation
注解 name
成员属性 getter
访问器。代码第4行 @MemberAnnotation
注解 age
成员属性。代码第5行 @get:MemberAnnotation
注解 age
成员属性 getter
访问器。第6行是使用 @MemberAnnotation
注解成员函数。
三、注解目标声明
上面案例代码的第3行和第5行都用到 @get:MemberAnnotation
形式的注解,其中 get
是声明 MemberAnnotation
注解应用在 name
成员属性 getter
访问器上,事实上一个 name
成员属性有很多可以被注解的元素,这些元素有:属性本身、getter访问器、setter访问器 和 支持字段。
Kotlin 中使用某个注解进行修饰时,当多个元素都有被修饰的可能时,可以使用注解目标声明指定注解修饰的具体元素,上面案例中的 get
(代码第3和第5行) 就是注解目标声明。Kotlin 中的注解目标列表如下:
目标 | 说明 |
---|---|
file | 文件 |
property | 属性,使用此目标Java中不可见 |
field | 字段 |
get | getter访问器 |
set | setter访问器 |
receiver | 扩展函数或属性的参数 |
param | 构造函数的参数 |
setparam | setter访问器参数 |
delegate | 保存委托属性的字段 |
从表中可见使用的 @file:JvmName("名称")
注解也使用了 file
目标声明。
四、案例:读取运行时注解信息
注解时为工具读取信息而准备的。有些工具可以读取源代码文件中的注解信息;有的可以读取字节码文件中的注解信息;有的可以在运行时读取注解信息。但是读取这些注解信息的代码都是一样的,区别只在于自定义注解中 @Retention
的保留期不同。
读取注解信息所需要的反射 API 是 KClass 类的 findAnnotation
函数。读取运行时 Student
类中注解信息代码如下:
fun main(args: Array) {
val clz = Student::class // 1
// 读取类注解
val ann = clz.findAnnotation() // 2
println("类${clz.simpleName},注解描述:${ann?.description ?: "无"}") // 3
// 读取成员函数的注解信息
println("---------- 读取成员函数的注解信息 ----------")
clz.declaredFunctions.forEach { // 4
val ann = it.findAnnotation() // 5
println("函数${it.name},注解描述:${ann?.description ?: "无"}")
}
// 读取属性的注解信息
println("---------- 读取属性的注解信息 ----------")
clz.declaredMemberProperties.forEach { // 6
val ann = it.findAnnotation() // 7
println("属性${it.name},注解描述:${ann?.description ?: "无"}")
}
}
运行结果:
类Student,注解描述:这是一个测试类
---------- 读取成员函数的注解信息 ----------
函数setNameAndAge,注解描述:设置姓名和年龄
函数toString,注解描述:无
---------- 读取属性的注解信息 ----------
属性age,注解描述:年龄
属性name,注解描述:名字
Process finished with exit code 0
上述代码第1行是创建 Student
类引用,代码第2行使用 findAnnotation
函数查找 Student
类是否有 MyAnnotation
。代码第3行中 ann?.description
表达式读取 MyAnnotation
注解中的 description
属性内容。
代码第4行 clz.declaredFunctions
返回当前 Student
类中所有成员函数的集合。代码第5行是查找 Student
成员函数是否有 MemberAnnotation
注解。
代码第6行 clz.declaredMemberProperties
返回当前 Student
类中所有成员的属性集合。代码第7行是查找 Student
成员属性是否有 MemberAnnotation
注解。