在源代码中嵌入一些补充信息,这种补充信息称为注解(Annotation)。注解并不能改变程序运行的结果,不会影响程序运行的性能。有些注解可以在编译时给用户提示或警告,有的注解可以在运行时读写字节码文件信息。
Kotlin中的注解本质上一种接口类型。Kotlin标准库中提供一些基本注解和元注解。基本注解会影响编译器的行为,如:@JvmName、@JvmField、@JvmStatic、@JvmOverloads和@Throws等,这些基本注解主要用于Kotlin与Java的混合编程中。元注解(Meta Annotation)是负责注解其他的注解,自定义注解会用到元注解。
Kotlin元注解有4个,其中包括@Target、@Retention、@Repeatable和@MustBeDocumented,它们都位于kotlin.annotation包中。元注解是为其他注解进行说明的注解,当自定义一个新的注解类型时,其中可以使用元注解。这一节先介绍一下这几种元注解含义,下一节在自定义注解中详细介绍它们的使用的。
1.@Target
@Target适用目标注解,对应注解类是kotlin.annotation.Target,它用来指定一个新注解的适用目标。@Target注解有一个allowedTargets属性,该属性用来设置适用目标,allowedTargets是kotlin.annotation.AnnotationTarget枚举类型的数组,AnnotationTarget描述Kotlin代码中可以被注解的元素类型,它有15个枚举常量,如表27-1所示。
1.@Retention
@Retention保留期注解,对应注解类是kotlin.annotation.Retention,它用来指定一个新注解的有效范围,@Retention注解有一个value属性,该属性用来设置保留期,value是kotlin.annotation.AnnotationRetention枚举类型,AnnotationRetention描述注解保留期种类,它有3个常量,如表27-2所示。
2.@Repeatable
@Repeatable可重复注解,对应注解类是kotlin.annotation.Repeatable,它允许在相同的程序元素中重复注解,可重复的注解必须使用@Repeatable进行注解。
3.@MustBeDocumented
@MustBeDocumented文档注解,对应注解类是kotlin.annotation.MustBeDocumented,该注解可以修饰代码元素(类、接口、函数和属性等),文档生成工具可以提取这些注解信息。
与基本注解不同,元注解在一般的应用开发中不会直接使用,在开发框架或生成工具时会用到一些自定义注解,元注解是自定义注解的组成要素。
声明自定义注解可以使用annotation关键字实现,最简单形式的注解示例代码如下:
annotation class Marker
上述代码声明一个Marker注解,annotation声明一个注解类型,注解的可见性有共有的、内部的和私有的,不能是保护的。
Marker注解中不包含任何的成员,这种注解称为标记注解(Marked Annotation),标记注解属于基本注解。注解也可以有成员属性,通过构造函数初始化成员属性。示例代码如下:
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 ①
class Person {
// 名字
@MyAnnotation1(value = “名字”) ②
@JvmField
var name =“Tony”
// 年龄
@MyAnnotation2(value = “年龄”) ③
var age = 18
}
@Marker ④
fun main(args: Array) {
@Marker ⑤
@MyAnnotation2(value= “实例化Person”,count = 1) ⑥
val p =Person()
}
默认情况下注解可以修饰任意的代码元素(类、接口、属性、变量、函数和数据类型等)。代码第①行、第④行和第⑤行都使用@Marker注解,分别修饰类、函数和变量。
代码第②行是使用@MyAnnotation1(value = “名字”)注解修饰name成员属性,其中value ="名字"是为value属性提供数值。代码第③行是使用@MyAnnotation2(value= “年龄”) 注解修饰成员函数,@MyAnnotation1有两个成员属性,但是只为count成员属性赋值,另外一个成员属性value是使用默认值。 代码第⑥行使用@MyAnnotation2(value = “实例化Person”, count = 1)注解修饰变量。
27.2.2 案例:使用元注解
上一节声明注解只是最基本形式的注解,对于复杂的注解可以在声明注解时使用元注解。下面通过一个案例介绍一下在自定义注解中使用元注解,在本案例中定义了两个注解。
首先看看第一个注解MyAnnotation,它用来注解类或接口,MyAnnotation代码如下:
//代码文件:chapter27/src/com/a51work6/section2/MyAnnotation.kt
package com.a51work6.section2
@MustBeDocumented ①
@Target(AnnotationTarget.CLASS) ②
@Retention(AnnotationRetention.RUNTIME) ③
annotation class MyAnnotation(val description: String) ④
上述代码第④行是声明注解类型MyAnnotation,其中使用了三个元注解修饰MyAnnotation注解,代码第①行使用@MustBeDocumented指定MyAnnotation注解信息可以被文档生成工具读取。代码第②行使用@Target(AnnotationTarget.CLASS)指定MyAnnotation注解用于修饰类和接口等类型。代码第③行@Retention(AnnotationRetention.RUNTIME)指定MyAnnotation注解信息可以在运行时被读取。代码第④行的description是MyAnnotation注解的属性。
第二个注解MemberAnnotation,它用来注解类中成员属性和函数,MemberAnnotation代码如下:
//代码文件:chapter27/src/com/a51work6/section2/MemberAnnotation.kt
package com.a51work6.section2
import kotlin.reflect.KClass
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME) ①
@Target(AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER) ②
annotation class MemberAnnotation(val type:
KClass<> = Unit::class, val description: String) ③
上述代码第③行是声明注解类型MemberAnnotation,其中也使用了三个元注解修饰MemberAnnotation注解,代码第①行的@Retention(AnnotationRetention.RUNTIME)指定MemberAnnotation注解信息可以在运行时被读取。代码第②行@Targe指定MemberAnnotation注解用于修饰类中成员(函数、属性、属性的getter访问器和属性的setter访问器)。
代码第③行中还声明MemberAnnotation注解属性type和description,type类型是KClass<>,默认值是Unit::class。description类型是String,没有设置默认值。
使用了MyAnnotation和MemberAnnotation注解是Student类,Student类代码如下:
//代码文件:chapter27/src/com/a51work6/section2/Student.kt
package com.a51work6.section2
@MyAnnotation(description = “这是一个测试类”) ①
class Student {
@MemberAnnotation(type = String::class, description = “名字”) ②
@set:MemberAnnotation(type= String::class, description = “获得名字”) ③
var name:String? = null
private set
@MemberAnnotation(type = Int::class, description = “年龄”) ④
@get:MemberAnnotation(type = Int::class, description = “获得年龄”) ⑤
var age: Int =0
private set
@MemberAnnotation(description = “设置姓名和年龄”) ⑥
fun setNameAndAge(name:String, age: Int) {
this.name =name
this.age =age
}
override fun toString(): String {
return"Person [name=$name, age=$age]"
}
}
使用注解时如果当前类与注解不在同一个包中,则需要将注解引入。代码第①行@MyAnnotation只能Student类声明之前。代码第②行@MemberAnnotation注解name成员属性,代码第③行@set:MemberAnnotation注解name成员属性setter访问器。代码第④行@MemberAnnotation注解age成员属性,代码第⑤行@set:MemberAnnotation注解age成员属性setter访问器。第⑥行是使用@MemberAnnotation注解成员函数。
27.2.3 注解目标声明
27.2.3节的案例代码第③行和第⑤行都用到@set:MemberAnnotation形式的注解,其中set是声明MemberAnnotation注解应用在name成员属性setter访问器上,事实上一个name成员属性有很多可以被注解的元素,这些元素有:属性本身、getter访问器、setter访问器和支持字段。
Kotlin中使用某个注解进行修饰时,当有多个元素被修饰的可能时,可以使用注解目标声明指定注解修饰的具体元素,27.2.2节的案例中的get(见代码第③行和第⑤行)就是注解目标声明。Kotlin的注解目标列表如表27-3所示。
从表中可见第21章使用的@file:JvmName(“PackageLevelDemo”)注解也使用了file目标声明。
27.2.4 案例:读取运行时注解信息
注解是为工具读取信息而准备的。有些工具可以读取源代码文件中的注解信息;有的可以读取字节码文件中的注解信息;有的可以在运行时读取注解信息。但是读取这些注解信息的代码都是一样的,区别只在于自定义注解中@Retention的保留期不同。
读取注解信息需要反射API是KClass类的findAnnotation函数。读取运行时Student类中注解信息代码如下:
//代码文件:chapter27/src/com/a51work6/section2/ch27.2.3.kt
package com.a51work6.section2
import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.findAnnotation
fun main(args: Array) {
val clz =Student::class ①
// 读取类注解
val ann =clz.findAnnotation() ②
println("类${clz.simpleName},注解描述:${ann?.description ?: "无"}") ③
// 读取成员函数的注解信息
println("–读取成员函数的注解信息–")
clz.declaredFunctions.forEach { ④
val ann =it.findAnnotation() ⑤
println(" 函数 i t . n a m e , 注 解 描 述 : {it.name},注解描述: it.name,注解描述:{ann?.description ?: “无”}")
}
// 读取属性的注解信息
println("–读取属性的注解信息–")
clz.declaredMemberProperties.forEach { ⑥
val ann =it.findAnnotation() ⑦
println(" 属性 i t . n a m e , 注 解 描 述 : {it.name},注解描述: it.name,注解描述:{ann?.description ?: “无”}")
}
}
运行结果如下:
类Student,注解描述:这是一个测试类
–读取成员函数的注解信息–
函数setNameAndAge,注解描述:设置姓名和年龄
函数toString,注解描述:无
–读取属性的注解信息–
属性age,注解描述:年龄
属性name,注解描述:名字
上述代码第①行是创建Student类引用,代码第②行使用findAnnotation函数查找Student类是否有MyAnnotation。代码第③行中ann?.description表达式读取MyAnnotation注解中description属性内容。
代码第④行clz.declaredFunctions返回当前Student类中所有成员函数集合。代码第⑤行是查找Student成员函数是否有MemberAnnotation注解。
代码第⑥行clz.declaredMemberProperties返回当前Student类中所有成员属性集合。代码第⑦行是查找Student成员属性是否有MemberAnnotation注解。
本章介绍了Kotlin的注解,主要介绍了元注解,以及自定义注解。读者需要掌握基本注解有哪些它们的用途,了解元注解、自定义注解,了解如何通过反射读取自定义注解信息。