kotlin注解

注解

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)

注解的属性可支持如下类型:

  1. 基本类型
  2. 字符串
  3. 类(如 Foo::class)
  4. 枚举
  5. 其他注解
  6. 上面各种类型的数组

一旦在注解中定义了属性之后 ,使用该属性时就应该为其指定属性值

class Item {
    //使用带属性的注解时,属性指定属性值,有默认值的可以不指定
    @MyTag(name = "kotlin")
    fun info() {

    }
}

注解分为两类

  1. 标记注解: 没有定义属性的注解被称为标记注解。这种注解仅利用自身的存在与否来提供信息
  2. 元数据注解: 包含属性的注解被称为元数据注解。因此它们可以接受更多的配置信息(以属性值的方式进行设置)

使用 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 多个程序单元,比如:

  1. 带属性声明的主构造器会变成 Java 的成员变量定义、 getter 方法、 setter 方法(如果是读写属性)、构造器参数
  2. 属性会变成 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() {

}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,367评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,959评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,750评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,226评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,252评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,975评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,592评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,497评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,027评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,147评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,274评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,953评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,623评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,143评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,260评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,607评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,271评论 2 358

推荐阅读更多精彩内容

  • 类比思维是非常实用的思维方式,我们跟Java的注解进行对比,就可以快速理解Kotlin的注解。 Java 代码: ...
    光剑书架上的书阅读 3,205评论 0 1
  • 声明并应用注解 注解只能拥有如下类型的参数:基本数据类型、字符串、枚举、类引用、其他的注解类,以及前面这些类型的数...
    蒋扬海阅读 846评论 0 0
  • Kotlin注解详解 注解声明注解是将元数据附加到代码的方法。要声明注解,请将 annotation 修饰符放在类...
    leofight阅读 1,477评论 0 1
  • 声明注解案例:使用元注解注解目标声明案例:读取运行时注解信息   与基本注解不同,在一般的应用开发中不会直接使用元...
    狼性代码人阅读 7,400评论 0 3
  • @Target@Retention@Repeatable@MustBeDocumented   Kotlin 中的...
    狼性代码人阅读 1,786评论 0 2