注解
Kotlin 的注解与 Java 的注解完全相同,也是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理
Kotlin 注解入门
Kotlin 使用 annotation class 关键字来定义注解
定义注解
Kotlin 不允许为注解定义注解体,可用于修饰程序中的类、方法、属性、接口等定义
//定义一个简单的注解
annotation class Test
//使用 @Test 修饰类定义
@Test
class MyClass {
//使用 @Test 注解修饰属性
@Test
var name: String = ""
//使用 @Test 注解修饰方法
@Test
fun info() {
}
}
如果要用注解来修饰主构造器,为主构造器添加 constructor 关键字
class User @Test constructor(var name : String, var pass: String) {
}
注解的属性和构造器
注解还可以带属性,由于注解没有注解体,因此注解的属性只能在注解声明部分指定。实际上,相当于在注解的主构造器中指定注解的属性
由于注解与普通类不同,注解的属性值只能在使用时指定,并且,一旦为注解的属性指定了属性值,以后就绝对不会改变其属性值,因此注解的属性只能定义为只读属性,注解的属性不能使用可空类型(不能在类型后添加“?”)
//可以在定义注解的属性时使用等号(=)为其指定初始值(默认值)
annotation class MyTag(val name: String, val age: Int = 4)
注解的属性可支持如下类型:
- 基本类型
- 字符串
- 类(如 Foo::class)
- 枚举
- 其他注解
- 上面各种类型的数组
一旦在注解中定义了属性之后 ,使用该属性时就应该为其指定属性值
class Item {
//使用带属性的注解时,属性指定属性值,有默认值的可以不指定
@MyTag(name = "kotlin")
fun info() {
}
}
注解分为两类
- 标记注解: 没有定义属性的注解被称为标记注解。这种注解仅利用自身的存在与否来提供信息
- 元数据注解: 包含属性的注解被称为元数据注解。因此它们可以接受更多的配置信息(以属性值的方式进行设置)
使用 vararg 修饰需要指定多个值的属性(相当于数组类型的属性),使用 vararg 修饰的属性,不需要指定属性名,是否指定属性名和方法参数的规则相似
annotation class MyTag(vararg val names: String, val age: Int)
class Item {
//对于使用 vararg 修饰的属性,不需要指定属性名,直接在注解中指定多个属性值即可
@MyTag("kotlin", "java", "web", age = 4)
fun info() {
}
}
如果将注解作为另一个注解的属性值,那么在使用注解时不需要以@作为前缀
annotation class MyTag(val value: String)
//该注解的 target 属性的类型是 MyTag
annotation class ShowTag(val message: String, val target: MyTag)
@ShowTag(message = "message 属性值", target = MyTag("kotlin"))
class Circle
如果需要将一个类作为注解的属性,请使用 Kotlin 类( KClass), Kotlin 编译器会自动将其转换为 Java 类,以便 Java 码能够正常看到该注解和参数
// arg1 的类型是 KClass<*>,这是星号投影用法,相当于 Java 的原始类型
// arg2 的类型是 KClass<out Any>,这是使用处协变的用法
//可传入 KClass<Int>、 KClass<String> 等,只要尖括号里的类型是 Any 的子类即可
annotation class DrawTag(
val arg1: KClass<*>,
val arg2: KClass<out Any>
)
@DrawTag(arg1 = String::class, arg2 = Int::class)
class Circle
元注解
使用@Retention
@Retention 只能修饰注解定义,用于指定被修饰的注解可以保留多长时间 。@Retention元注解包含 AnnotationRetention 类型的 value 属性,所以使用 Retention 时必须为 value 属性指定值
value 属性的值 | 说明 |
---|---|
AnnotationRetention.SOURCE | 注解只保留在源代码中,编译器直接丢弃这种注解 |
AnnotationRetention.BINARY | 编译器将把注解记录在 class 文件中 。当运行该字节码文件时, JVM 不可获取注解信息 |
AnnotationRetention.RUNTIME | 编译器将把注解记录在 class 文件中 。当运行该字节码文件时, JVM 可获取注解信息,程序可以通过反射获取该注解信息。这是默认值 |
//下面定义的@Test注解保留到运行时
@Retention(value = AnnotationRetention.RUNTIME)
annotation class Test
使用@Target
@Target 也只能修饰注解定义,用于指定被修饰的注解能修饰哪些程序单元。@Target 注解包含 一个类型为 AnnotationTarget 数组的 allowedTargets 属性,该属性的值只能是如下几个值组成的数组
allowedTargets 属性的值 | 说明 |
---|---|
AnnotationTarget.CLASS | 修饰类 |
AnnotationTarget.ANNOTATION_CLASS | 修饰注解 |
AnnotationTarget.TYPE_PARAMETER | 修饰泛型形参 |
AnnotationTarget.PROPERTY | 修饰属性 |
AnnotationTarget.FIELD | 修饰字段(包括属性的幕后字段) |
AnnotationTarget.LOCAL_VARIABLE | 修饰局部变量 |
AnnotationTarget.VALUE_PARAMETER | 修饰函数或构造器的形参 |
AnnotationTarget.CONSTRUCTOR | 修饰构造器 |
AnnotationTarget.FUNCTION | 修饰函数和方法(不包含构造器) |
AnnotationTarget.PROPERTY_GETTER | 修饰属性的 getter方法 |
AnnotationTarget. PROPERTY_SETTER | 修饰属性的 setter方法 |
AnnotationTarget.TYPE | 修饰类型 |
AnnotationTarget.EXPRESSION | 修饰各种表达式 |
AnnotationTarget.FILE | 修饰文件 |
AnnotationTarget.TYPEALIAS | 修饰类型别名 |
//下面定义的@Test注解保留到运行时
@Retention(value = AnnotationRetention.RUNTIME)
//指定@Test注解只能修饰函数和类,如下
//@Target(allowedTargets = [AnnotationTarget.FUNCTION, AnnotationTarget.CLASS])
//也可以去掉allowedTargets属性名
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
annotation class Test
使用@MustBeDocumented
使用 @MustBeDocumented 元注解修饰的注解将被文档工具提取到 API 文档中,则所有使用该注解修饰的程序元素 API 文档中将会包含该注解说明
//下面定义的@Test注解保留到运行时
@Retention(value = AnnotationRetention.RUNTIME)
//指定@Test注解只能修饰函数和类,如下
//@Target(allowedTargets = [AnnotationTarget.FUNCTION, AnnotationTarget.CLASS])
//也可以去掉allowedTargets属性名
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
//定义@Test注解将被文档工具所提取
@MustBeDocumented
annotation class Test
使用@Repeatable 标记可重复注解
Kotlin 允许使用多个相同的注解来修饰同一个程序单元,这种注解称为可重复注解,可重复注解的@Retention 策略只能指定为 AnnotationRetention.SOURCE
//下面定义的@Test注解保留到运行时
@Retention(value = AnnotationRetention.SOURCE)
//指定@Test注解只能修饰函数和类,如下
//@Target(allowedTargets = [AnnotationTarget.FUNCTION, AnnotationTarget.CLASS])
//也可以去掉allowedTargets属性名
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
//定义@Test注解将被文档工具所提取
@MustBeDocumented
//指定该注解是可重复注解
@Repeatable
annotation class Test
使用注解
提取注解信息
使用注解修饰类、方法、属性等成员之后,这些注解不会自己生效,必须由开发者提供相应的工具来提取并处理注解信息
Kotlin 使用 kotlin.Annotation 接口来代表程序元素前面的注解,该接口是所有注解的父接口,Kotlin 在 kotlin.reflect 包下新增了 KAnnotatedElement 接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类
KAnnotatedElement 接口实现类 | 说明 |
---|---|
KCallable | 代表可执行的程序实体,如函数和属性 |
KClass | 代表 Kotlin 的类、接口等类型 |
KParameter | 代表函数和属性的参数 |
KAnnotatedElement方法:
KAnnotatedElement方法 | 说明 |
---|---|
annotations: List<Annotation> | 该属性返回该程序单元上所有的注解 |
<T: Annotation> findAnnotation(): ? | 根据注解类型返回该程序单元上特定类型的注解。如果该类型的注解不存在,则该方法返回 null |
获取 Test 类中修饰 info() 方法的所有注解
annotation class MyTag(val value: String)
class Test {
@MyTag("kotlin")
fun info() {
}
}
val annotations = Test::info.annotations
//遍历所有注解
for (annotation in annotations) {
Log.d(TAG, "onCreate: annotation=$annotation")
}
获取某个注解里的元数据,可以将注解转型成所需的注解类型,然后通过注解对象的属性来访问这些元数据
//遍历所有注解
for (annotation in annotations) {
Log.d(TAG, "onCreate: annotation=$annotation")
//如果 tag 注解是 MyTag 类型
if (annotation is MyTag) {
//输出注解的 value 属性的值
Log.d(TAG, "onCreate: annotation=${annotation.value}")
}
}
Java 注解与 Kotlin 的兼容性
Java 注解与 Kotlin 完全兼容
指定注解的作用目标
Kotlin 程序的一个程序单元有时候会变成 Java 多个程序单元,比如:
- 带属性声明的主构造器会变成 Java 的成员变量定义、 getter 方法、 setter 方法(如果是读写属性)、构造器参数
- 属性会变成 Java 的成员变量定义、 getter 方法、 setter 方法(如果是读写属性)
这样就产生了一个问题,有时候我们只想用注解修饰特定的程序单元,比如只希望用注解修饰属性对应的幕后字段,或者只希望用注解修饰属性对应的 getter 方法
此时就需要为注解指定作用目标,语法格式如下:
@目标:注解(注解属性值)
如果在同一个目标上要指定多个注解,则需要将多个注解放在方括号中,并用空格隔开,语法格式:
@目标:[注解1(注解属性值) 注解2(注解属性值)...]
Kotlin 支持的目标包含如下几个
Kotlin 支持的目标 | 说明 |
---|---|
file | 指定注解对文件本身起作用 |
property | 指定注解对整个属性起作用(这种目标的注解对 Java 不可见,因为 Java 并没有真正的属性 |
field | 指定注解对属性的幕后字段起作用 |
get | 指定注解对属性的 getter 方法起作用 |
set | 指定注解对属性的 setter 方法起作用 |
receiver | 指定注解对扩展方法或扩展属性的接收者起作用 |
param | 指定注解对构造器的参数起作用 |
setparam | 指定注解对 setter 方法的参数起作用 |
delegate | 指定注解对委托属性存储其委托实例的字段起作用 |
annotation class MyTag
annotation class FkTag(val info: String)
class Item {
//指定注解只对 getter 方法起作用
//对 getter 方法应用了两个注解: MyTag FkTag
@get: [MyTag FkTag("kotlin")]
var name: String = "java"
}
如果要指定注解作用于整个文件本身,则必须将注解放在 package 语句(如果有 package语句)之前,或者所有导包语句之前(如果没有 package 语句)
@Target(AnnotationTarget.FILE)
annotation class FileTag(val value:String)
//指定 @FileTag 注解作用于整个文件
@file: FileTag ("kotlin")
package com.example.kotlin
如果要指定注解作用于扩展方法或扩展属性的接收者,则使用带 receiver: 的注解修饰整个扩展方法或扩展属性即可
// 指定 @MyTag 注解作用于扩展方法的接收者(String)
fun @receiver:MyTag String.test() {}
使用 Java 注解
Java 注解的成员变量(相当于 Kotlin 注解的属性,后文统称为“属性”)是没有顺序的,因此只能通过属性名来设置属性值
先定义一个 Java 注解
public interface JavaTag {
public String name() ;
public int age();
}
再定义一个 Kotlin 注解
annotation class KotlinTag(val name : String, val age : Int)
//Kotlin 注解可通过位置来指定属性值
//第一个值传给 name 属性,第二个值传给 age 属性
@KotlinTag("kotlin", 4)
public class Book {
// Kotlin 注解也可通过属性名来指定属性值
@KotlinTag("kotlin", 4)
// Java 注解只能通过属性名来指定属性值
@JavaTag(name = "java", age = 6)
fun test() {
}
}
如果 Java 注解中的 value 属性是数组类型,那么它会变成 Kotlin 注解的 vararg 属性,因此直接为它传入多个属性值即可
public @interface JavaTagWithArray {
public String[] value();
}
//直接传入多个属性值
@JavaTagWithArray("kotlin", "java")
fun test() {
}
但如果 Java 注解中属性是数组类型,并且不是 value 属性名,那么在 Kotlin 中使用该注解时必须显式使用 arrayOf() 函数来构建数组或者 [] 符号
public @interface JavaTagWithArray {
public String[] infos();
}
@JavaTagWithArray(infos = ["kotlin", "java"])
//或者是下面的使用方法
//@JavaTagWithArray(infos = arrayOf("kotlin", "java"))
fun test() {
}