Kotlin注解(2)自定义注解

  • 声明注解
  • 案例:使用元注解
  • 注解目标声明
  • 案例:读取运行时注解信息

  与基本注解不同,在一般的应用开发中不会直接使用元注解,在开发框架或生成工具时会用到一些自定义注解,元注解是自定义注解的组成要素。

一、声明注解

  声明自定义注解可以使用 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<String>) {
    @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行的 descriptionMyAnnotaion 注解的属性。

  • 第二个注解,用来注解类中成员属性和函数
@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 注解的属性是 typedescriptiontype 类型是 KClass<*>,默认值是 Unit::classdescription 类型是 String,没有设置默认值。

  提示:代码第3行的 KClass<*> 类型表示 KClass 的泛型,* 是泛型通配符,可以是任何类型。多数情况下泛型尖括号中指定的都是某个具体类型,泛型也是为此而设计的。但是有时确实不需要知道具体类型,或者说什么类型都可以,此时可以使用 * 通配所有类型。

  • 使用 MyAnnotationMemberAnnotation 注解的 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<String>) {
    val clz = Student::class        // 1

    // 读取类注解
    val ann = clz.findAnnotation<MyAnnotation>()    // 2
    println("类${clz.simpleName},注解描述:${ann?.description ?: "无"}")   // 3

    // 读取成员函数的注解信息
    println("---------- 读取成员函数的注解信息 ----------")
    clz.declaredFunctions.forEach {     // 4
        val ann = it.findAnnotation<MemberAnnotation>()     // 5
        println("函数${it.name},注解描述:${ann?.description ?: "无"}")
    }

    // 读取属性的注解信息
    println("---------- 读取属性的注解信息 ----------")
    clz.declaredMemberProperties.forEach {      // 6
        val ann = it.findAnnotation<MemberAnnotation>()     // 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 注解。

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

推荐阅读更多精彩内容

  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,756评论 2 9
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,101评论 1 32
  • 定义注解 Kotlin使用 annotation class 关键字(就像使用 enum class 定义枚举类一...
    凌寒天下独自舞阅读 936评论 0 0
  • 面向对象编程(OOP) 在前面的章节中,我们学习了Kotlin的语言基础知识、类型系统、集合类以及泛型相关的知识。...
    Tenderness4阅读 4,441评论 1 6
  • 1. 走近你并非因都是天涯行客 数尽缤纷心中只有一道景色 2. 一生中有许多相遇 最快乐的相遇是认识了你 一生中有...
    洛长禾阅读 347评论 0 2