注解

作者: 凌寒天下独自舞 | 来源:发表于2018-11-06 18:17 被阅读0次

    定义注解

    Kotlin使用 annotation class 关键字(就像使用 enum class 定义枚举类一样),定义注解非常简单,Kottin 甚至不允许为注解定义注解体,也就是说,注解后面不能有花括号。

    //定义一个简单的注解
    annotation class MyClass
    

    定义了该注解之后,就可以在程序的任何地方使用该注解。使用注解的语法非常类似于使 用 public、 final 这样的修饰符,通常可用于修饰程序中的类、方法、属性、接口等定义。通常会把注解放在所有修饰符之前。

    //使用自Test 修饰类定义
    @Test
    class Demo1 {
        //使用自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)
    

    使用 annotation class 定义的注解其实就相当于定义了一个注解接口,这个注解接口继承了kotlin.Annotation接口。
    需要说明的是,注解的属性不能使用可空类型(不能在类型后添加“?”),这是因为JVM本身不允许使用 null作为注解的属性值。

    一旦在注解中定义了属性之后 ,使用该属性时就应该为其指定属性值 ,如下面代码所示

    class Item {
        //使用带属性的注解时,需要为属性指定属性值 
        @MyTag(name="xx", age=6)
        fun info() {
        }
    }
    

    也可以在定义注解的属性时使用等号(=)为其指定初始值(默认值)(就像定义类时在主构造器中为类的属性指定初始值 一样) ,注解的初始值只能是编译时常量。如果为注解的属性指定了默认值,那么在使用该注解时可以不为这些属性指定值,而是直接使用默认值。

    根据注解是否可以包含属性,可以把注解分为如下两类。

    • 标记注解: 没有定义属性的注解被称为标记注解。这种注解仅利用自身的存在与否来提供信息,如前面所介绍的@Test等注解。
    • 元数据注解: 包含属性的注解被称为元数据注解。因此它们可以接受更多的配置信息(以属性值的方式进行设置) 。 如前面所介绍的@MyTag等注解。

    与 Java类似的是,如果注解的属性名为 value,则为 value属性指定属性值时可省略属性名。

    Kotlin使用 vararg修饰需要指定多个值的属性(相当于数组类型的属性),也可以不带属性名。

    如果将一个注解作为另一个注解的属性值,那么在使用注解时不需要以@作为前缀。

    //定义带 value 属性的注解
    annotation class MyTag(val value: String)
    
    //该注解的 target 属性的类型是 MyTag
    annotation class showTag(val message:String,val tag:MyTag)
    
    
    @showTag(message = "SS",tag = MyTag("ZZZ"))
    class Demo1
    

    如果需要将一个类作为注解的属性,请使用 Kotlin 类( KClass), Kotlin 编译器会自动将 其转换为 Java类,以便 Java代码能够正常看到该注解和参数 。

    // tag1 的类型是 KClass<*>,这是星号投影用法,相当于 Java 的原始类型
    // tag2 的类型是 KClass<out Any>,这是使用处协变的用法
    //可传入 KClass<Int>、 KClass<String〉等,只要尖括号里的类型是 Any 的子类即可
    annotation class DrawTag(val tag1:KClass<*>,val tag2:KClass<out Any>)
    
    @DrawTag(tag1 = String::class,tag2 = Int::class)
    class Demo1
    

    元注解

    Kotlin 在 kotlin.annotation 包下提供了4个Meta 注解(元注解),这4个元注解都用于修饰其他的注解定义。

    使用@ Retention

    @Retention只能修饰注解定义,用于指定被修饰的注解可以保留多长时间 。@Retention元注解包含一个 AnnotationRetention类型的 value属性,所以使用@Retention时必须为该value 属性指定值。
    value 属性的值只能是如下 3 个 。

    • AnnotationRetention.SOURCE: 注解只保留在源代码中,编译器直接丢弃这种注解 。
    • AnnotationRetention.BINARY: 编译器将把注解记录在 class 文件中 。当运行该字节码
      文件时, JVM 不可获取注解信息。
    • AnnotationRetention.RUNTIME: 编译器将把注解记录在 class文件中。当运行该字节
      码文件时, JVM也可获取注解信息,程序可以通过反射获取该注解信息。这是默认值。

    如果要通过反射获取注解信息,就需要使用 value属性值为 AnnotationRetention.RUNTIME
    的@Retention (或省略该元注解)。使用@Retention元注解可采用如下代码为value指定值。

    //下面定义的 Testable 注解保留到运行时
    @Retention(value = AnnotationRetention.RUNTIME)
    annotation class Test
    

    使用@Target

    @Target 也只能修饰注解定义,用于指定被修饰的注解能修饰哪些程序单元。@Target 元注解包含一个类型为 AnnotationTarget 数组的 allowedTargets 属性,该属性的值只能是如下几个值组成的数组。

    • Annotation Target.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 方法。
    • Annotation Target.TYPE: 指定该策略的注解只能修饰类型。
    • AnnotationTarget.EXPRESSION: 指定该策略的注解只能修饰各种表达式。
    • AnnotationTarget.FILE: 指定该策略的注解只能修饰文件。
    • AnnotationTarget.TYPEALIAS:指定该策略的注解只能修饰类型别名。

    与使用@Retention 类似的是,使用@Target 也可以直接在括号里指定value 值,而无须使用name=value 的形式。如下代码指定@ActionListenerFor注解只能修饰属性。

    //指定@ActionListenerFor注解只能修饰属性
    @Target(allowedTargets = AnnotationTarget.PROPERTY)
    annotation class ActionListenerFor
    

    使用@MustBeDocumented

    使用@MustBeDocumented元注解修饰的注解将被文档工具提取到API文档中,如果定义注解类时使用了@MustBeDocumented修饰,则所有使用该元注解修饰的程序元素的API文档中将会包含该注解说明 。
    下面代码定义了一个@Testable 注解,程序使用@MustBeDocumented 修饰该注解,所以@Testable 注解将被文档工具所提取。

    //指定@ActionListenerFor注解只能修饰属性
    @Retention(value = AnnotationRetention.RUNTIME)
    @Target(AnnotationTarget.FUNCTION)
    //定义@Testable 注解将被文档工具所提取
    @MustBeDocumented
    annotation class Testable
    

    上面代码指定了文档工具生成 API 文档时将提取@Testable的使用信息。下面代码定义了一个 MyTest类,该类中的 info()方法使用了@Testable修饰。

    class MyTest {
       //使用自Testable修饰info()方法
       @Testable
       private fun info() {
           println("info")
    
       }
    }
    

    使用@Repeatable标记可重复注解

    Kotlin允许使用多个相同的注解来修饰同一个程序单元,这种注解称为可重复注解 。
    开发可重复注解需要使用@Repeatable修饰,下面通过示例来介绍如何开发可重复注解。首先定义一个@FkTag注解。

    //指定@ActionListenerFor注解只能修饰属性
    @Retention(value = AnnotationRetention.SOURCE)
    @Target(AnnotationTarget.CLASS)
    @Repeatable
    annotation class FkTag(val name:String="kotlin",val age:Int)
    

    上面定义了@FkTag注解,该注解包含两个属性。程序还使用了@Repeatable 来修饰该注解,这意味着它是一个可重复注解,因此可直接使用多个@FkTag 注解修饰目标程序单元。

    @FkTag(name = "xq",age = 24)
    @FkTag(age = 4)
    class MyTest {
        private fun info() {
            println("info")
    
        }
    }
    

    需要说明的是,由于在Java 8 之前JVM 并不支持可重复注解,Kotlin 也没有办法突破该限制,因此可重复注解的@Retention 策略只能指定为 AnnotationRetention.SOURCE,这意味着可重复注解只能被 Kotlin 编译器读取,接下来 Kotlin 编译器会直接丢弃该注解信息。

    使用注解

    提取注解信息

    使用注解修饰类、方法、属性等成员之后,这些注解不会自己生效,必须由开发者提供相应的工具来提取并处理注解信息。
    Kotlin使用 kotlin.Annotation接口来代表程序元素前面的注解,该接口是所有注解的父接口。Kotlin 在kotlin.reflect 包下新增了KAnnotatedElement接口,该接口代表程序中可以接受注解的程序元素。该接口主要有如下几个实现类。

    • KCallable: 代表可执行的程序实体,如函数和属性。
    • KClass:代表 Kotlin 的类、接口等类型。
    • KParameter: 代表函数和属性的参数。

    在kotlin.reflect包下主要包含一些实现反射功能的工具类,该包所提供的反射API包含了读取运行时注解的能力。只有当定义注解时使用了@Retention(AnnotationRetention.RUNTIME) 修饰,该注解才会保留到程序运行时,JVM 才会在装载* .class 文件时读取保存在 class 文件中的注解。

    KAnnotatedElement接口是所有程序元素(如 KClass、 KCallable、 KParameter)的父接口, 所以程序通过反射获取了某个程序单元对应的KAnnotatedElement对象(如 KClass、KCallable、 KParameter)之后,程序就可以调用该对象的如下属性和方法来访问注解信息。

    • annotations: List<Annotation>: 该属性返回该程序单元上所有的注解。
    • <T: Annotation> findAnnotation(): T?: 根据注解类型返回该程序单元上特定类型的注解。如果该类型的注解不存在,则该方法返回 null。

    下面代码片段用于获取 Test类中修饰 info()方法的所有注解,井将这些注解打印出来。

    //指定@ActionListenerFor注解只能修饰属性
    @Retention(value = AnnotationRetention.RUNTIME)
    @Target(AnnotationTarget.FUNCTION)
    //定义@Testable 注解将被文档工具所提取
    @MustBeDocumented
    annotation class Testable
    
    
    class MyTest {
        //使用自Testable修饰info()方法
        @Testable
        public fun info() {
            println("info")
    
        }
    }
    
    fun main(args: Array<String>) {
        val aArray = MyTest::info.annotations
        //遍历所有注解
        for (an in aArray){
            println(an)
        }
    }
    

    如果需要获取某个注解里的元数据,则可以将注解转型成所需的注解类型,然后通过注解对象的属性来访问这些元数据。代码如下 :

    //指定@ActionListenerFor注解只能修饰属性
    @Retention(value = AnnotationRetention.RUNTIME)
    @Target(AnnotationTarget.FUNCTION)
    //定义@Testable 注解将被文档工具所提取
    @MustBeDocumented
    annotation class Testable(val name:String="xq",val age:Int =24)
    
    
    class MyTest {
        //使用自Testable修饰info()方法
        @Testable(name = "sq",age = 23)
        fun info() {
            println("info")
    
        }
    }
    
    fun main(args: Array<String>) {
        val aArray = MyTest::info.annotations
        //遍历所有注解
        for (an in aArray){
            println(an)
            if(an is Testable){
                println(an.name)
                println(an.age)
            }
        }
    }
    

    下面分别介绍两个使用注解的例子。第 一个例子中的@Testable 注解没有任何属性,它仅是一个标记注解,其作用是标记哪些方法需要测试。

    @Retention(AnnotationRetention.RUNTIME)
    @Target(AnnotationTarget.FUNCTION)
    //定义一个标记注解,不包含任何属性
    annotation class Testable
    
    class Test {
        //使用@ Testable 注解指定该方法是需要测试的
        @Testable
        fun m1() {
    
        }
    
        fun m2() {
    
        }
    
        //使用@ Testable 注解指定该方法是需要测试的
        @Testable
        fun m3() {
    
        }
    
        fun m4() {
    
        }
    
        //使用@ Testable 注解指定该方法是需要测试的
        @Testable
        fun m5() {
    
        }
    
        fun m6() {
    
        }
    
        //使用@ Testable 注解指定该方法是需要测试的
        @Testable
        fun m7() {
    
        }
    
        fun m8() {
    
        }
    }
    

    正如前面所提到的,仅仅使用注解来标记程序元素对程序是不会有任何影响的,这也是注解的一条重要原则。为了让程序中的这些注解起作用,接下来必须为这些注解提供一个注解处理工具。

    下面的注解处理工具会分析目标类,如果目标类中的方法使用了@Testable 注解修饰,则通过反射来运行该测试方法。

    @Retention(AnnotationRetention.RUNTIME)
    @Target(AnnotationTarget.FUNCTION)
    //定义一个标记注解,不包含任何属性
    annotation class Testable
    
    class Test {
        //使用@ Testable 注解指定该方法是需要测试的
        @Testable
        fun m1() {
    
        }
    
        fun m2() {
    
        }
    
        //使用@ Testable 注解指定该方法是需要测试的
        @Testable
        fun m3() {
            throw RuntimeException ("参数出错了! ")
        }
    
        fun m4() {
    
        }
    
        //使用@ Testable 注解指定该方法是需要测试的
        @Testable
        fun m5() {
    
        }
    
        fun m6() {
    
        }
    
        //使用@ Testable 注解指定该方法是需要测试的
        @Testable
        fun m7() {
            throw RuntimeException ("程序业务出现异常! ")
        }
    
        fun m8() {
    
        }
    }
    
    inline fun <reified T : Any> processTestable() {
        var  passed =0
        var failed = 0
        val target = T::class.createInstance()
        //遍历 T 对应的类里的所有方法
        for (m in T::class.functions) {
            //如果该方法使用了@ Testable 修饰
            if (m.findAnnotation<Testable>() != null) {
                try{
                    //调用 m方法
                    m.call(target)
                    //测试成功, passed 计数器加 1
                    passed++
                }catch (ex:Exception){
                    println ("方法"+ m +"运行失败,异常:" + ex.cause)
                    //测试出现异常, failed 计数器加 1
                    failed++
    
                }
            }
        }
    
        //统计测试结果
        println ("共运行了:"+ (passed + failed) +"个方法,其中:\n"+"失败了:"+ failed +"个,\n" +"成功了:"+ passed +"个!")
    }
    
    fun main(args: Array<String>) {
        //处理 MyTest 类
        //运行结果:方法fun test10.Test.m3(): kotlin.Unit运行失败,异常:java.lang.RuntimeException: 参数出错了! 
        //方法fun test10.Test.m7(): kotlin.Unit运行失败,异常:java.lang.RuntimeException: 程序业务出现异常! 
        //共运行了:4个方法,其中:
        //失败了:2个,
        //成功了:2个!
        processTestable<Test>()
    }
    

    上面程序定义了 一个<reifiedT: Any> processTestable()函数, 该函数可接收一个泛型参数, 分析该泛型参数所代表的类,并运行该目标类中使用@Testable修饰的方法。

    前面介绍的只是一个标记注解,程序通过判断该注解存在与否来决定是否运行指定方法。下面程序通过使用注解来简化事件编程。在传统的事件编程中总是需要通过addActionListener() 方法来为事件源绑定事件监听器,本示例则通过@ActionListenerFor 来为程序中的按钮绑定事件监听器。

    package test10
    
    import java.awt.event.ActionEvent
    import java.awt.event.ActionListener
    import javax.swing.JButton
    import javax.swing.JFrame
    import javax.swing.JOptionPane
    import javax.swing.JPanel
    import javax.swing.WindowConstants.EXIT_ON_CLOSE
    import kotlin.reflect.KClass
    
    //指定该注解只能修饰属性
    @Target(AnnotationTarget.PROPERTY)
    @Retention(AnnotationRetention.RUNTIME)
    //定义一个属性,用于设置元数据
    //该 listener 属性用于保存监听器实现类
    annotation class ActionListenerFor(val listener: KClass<out ActionListener>)
    
    class AnnotationTest {
        val mainWin = JFrame("使用注解绑定事件监听器")
        //使用注解为 ok 按钮绑定事件监听器
        @ActionListenerFor(listener = OkListener::class)
        val ok = JButton("确定")
    
        //使用注解为 cancel 按钮绑定事件监听器
        @ActionListenerFor(listener = CancelListener::class)
        val cancel = JButton("取消")
    
        fun init() {
            //初始化界面的方法
            val jp = JPanel()
            jp.add(ok)
            jp.add(cancel)
            mainWin.add(jp)
            processAnnotations(this)
            mainWin.defaultCloseOperation = EXIT_ON_CLOSE
            mainWin.pack()
            mainWin.isVisible = true
        }
    }
    
    //定义 ok 按钮的事件监听器实现类
    class OkListener : ActionListener {
        override fun actionPerformed(e: ActionEvent?) {
            JOptionPane.showMessageDialog(null, "单击了确认按钮")
        }
    
    }
    
    //定义cancel按钮的事件监听器实现类
    class CancelListener : ActionListener {
        override fun actionPerformed(e: ActionEvent?) {
            JOptionPane.showMessageDialog(null, "单击了取消按钮")
        }
    
    }
    
    fun main(args: Array<String>) {
        AnnotationTest().init()
    }
    
    //处理注解的方法,其中 obj 是包含注解的对象
    fun processAnnotations(obj: Any) {
        //获取 obj 对象的类
        val cl = obj::class
        //获取指定 obj对象的所有成员,并遍历每个成员
        for (prop in cl.memberProperties) {
            //获取该成员上 ActionListenerFor 类型的注解
            val a = prop.findAnnotation<ActionListenerFor>()
            //获取属性 prop 的值
            val fObj = prop.call(obj)
    
            //如果 fObj 是 AbstractButton的实例,且 a 不为 null
            if (a != null && fObj != null && fObj is AbstractButton) {
                //获取 a 注解的 listener 属性值〈它是一个监听器类〉
                val listenerClazz = a.listener
                //使用反射来创建 listener 类的对象
                val al = listenerClazz.createInstance()
                //为 fObj 按钮添加事件监听器
                fObj.addActionListener(al)
            }
    
        }
    }
    

    上面代码定义了两个 JButton 按钮,并使用@ActionListenerFor 注解为这两个按钮绑定了事件监听器。使用@ActionListenerFor 注解时传入了 listener 元数据,该元数据用于设定每个按钮的监听器实现类。

    正如前面所提到的,如果仅在程序中使用注解是不会起任何作用的,必须使用注解处理工具来处理程序中的注解。 上面代码使用了 processAnnotations()函数来处理注解,该处理器分析目标对象中的所有属性,如果在属性前使用了@ActionListenerFor 修饰,则取出该注解中的listener元数据,并根据该元数据来绑定事件监听器。

    Java 注解与 Kotlin 的兼容性

    Java 注解与 Kotlin 完全兼容,只是在使用时略加注意即可。

    指定注解的作用目标

    根据前面的介绍我们知道, Kotlin程序往往比较简洁, Kotlin程序的一个程序单元有时候会变成Java的多个程序单元。比如:

    • 带属性声明的主构造器会变成 Java 的成员变量定义、 getter方法、 setter方法(如果是读写属性)、构造器参数 。
    • 属性会变成 Java 的成员变量定义、 getter方法、 setter方法(如果是读写属性) 。

    这样就产生了一个问题: 有时候我们只想用注解修饰特定的程序单元,比如只希望用注解修饰属性对应的幕后字段,或者只希望用注解修饰属性对应的getter方法,那该怎么办呢?

    此时就需要为注解指定作用目标,语法格式如下:

    @目标 :注解(注解属性值)

    如果在同 一个目标上要指定多个注解,则需要将多个注解放在方括号中,并用空格隔开,语法格式如下:

    @目标 : [注解 1 (注解属性值)注解 2 (注解属性值), . . . ]

    从上面的语法格式不难看出,为注解指定作用目标,其实就是在@符号和注解之间添加目标名和冒号。 Kotlin支持的目标包含如下几个。

    • file: 指定注解对文件本身起作用 。
    • property: 指定注解对整个属性起作用(这种目标的注解对 Java 不可见,因为 Java 并没有真正的属性) 。
    • field: 指定注解对属性的幕后字段起作用。
    • get: 指定注解对属性的 getter方法起作用。
    • set: 指定注解对属性的 setter方法起作用 。
    • receiver: 指定注解对扩展方法或扩展属性的接收者起作用 。
    • param: 指定注解对构造器的参数起作用。
    • setparam: 指定注解对 setter方法的参数起作用。
    • delegate: 指定注解对委托属性存储其委托实例的字段起作用。

    下面先看一个简单的例子,程序指定注解只对属性的 getter方法起作用。

    annotation class MyTag
    annotation class FkTag(val info: String)
    
    class Item {
        //指定注解只对 getter 方法起作用
        //对 getter 方法应用了两个注解: MyTag、 FkTag
        @get:[MyTag FkTag(info = "补充信息")]
        var name = "kotlin"
    }
    
    fun main(args: Array<String>) {
    
        //获取Item类对应的Java类 (Class对象)
        val clazz = Item::class.java
        //遍历 clazz 类所包含的全部方法
        for (mtd in clazz.declaredMethods) {
            println("一方法${mtd}上的注解如下一")
            //遍历该方法上直接声明的所有注解
            for (an in mtd.declaredAnnotations) {
                println(an)
            }
        }
    
        //遍历 clazz 类所包含的全部成员变量
        for (f in clazz.declaredFields) {
            println("一属性${f}上的注解如下一")
            //遍历该成员变盘上直接声明的所有注解
            for (an in f.declaredAnnotations) {
                println(an)
            }
        }
    }
    

    上面代码指定 name属性的 getter方法应用了两个注解,其实就是在原来的注解用法前增加@get:部分,与原来不指定目标的注解区别并不大。

    程序后面的 main()函数主要就是分析 Item 类中各方法、成员变量的注解 。 由于要分析成员变量上的注解,因此 main()函数使用了 Java 的反射 API (由于 Kotlin 并不支持单独 定义成员变量,因此 Kotlin 的反射 API 不支持直接操作成员变量),通过该 main()的运行可以看到程序中添加的两个注解只作用于属性的getter方法上 。

    如果要指定注解作用于整个文件本身,则必须将注解放在 package 语句(如果有 package语句)之前,或者所有导包语句之前(如果没有package 语句) 。代码如下 :

    //指定自 FileTag 注解作用于整个文件
    @file: FileTag ("yeeku") 
    package org.crazyit.demo
    

    如果要指定注解作用于扩展方法或扩展属性的接收者,则使用带 receiver:的注解修饰整个扩展方法或扩展属性即可。例如如下代码:

    // 指定@MyTag 注解作用于扩展方法的接收者(String) 
    fun @receiver:MyTag String.fun() {}
    

    使用Java注解

    Kotlin完全兼容Java注解,因此可以直接在 Kotlin程序中使用Java注解。
    需要说明的是,Java注解的成员变量 (相当于 Kotlin 注解的属性,后文统称为“属性”) 是没有顺序的,因此只能通过属性名来设置属性值 CKotlin注解的属性还可通过位置来设置属性值) 。

    对比如下两个注解。 下面先定义一个 Java注解。

    public @interface JavaTag {
        String name();
    
        int age();
    }
    

    下面再定义一个 Kotlin注解。

    annotation class KotlinTag(val name:String,val age:Int)
    

    上面两个注解基本相同,它们都包含了 name 和 age 两个属性 。接下来在程序中使用这两个注解就可看到它们的区别 。

    //Kotlin 注解可通过位置来指定属性值
    //第一个值传给 name 属性,第二个值传给 age 属性
    @KotlinTag("kotlin", 4)
    class Book {
        //Kotlin 注解也可通过属性名来指定属性值
        @KotlinTag(name = "xq", age = 24)
        //Java注解只能通过属性名来指定属性值
        @JavaTag(name = "xy", age = 22)
        fun test() {
            
        }
    }
    

    Kotlin 注解既支持使用位置来指定属性值(第一个值传给第一个属性,第二个值传给第二个属性,依此类推),也支持使用属性名来指定属性值(这是传统 Java注解的使用方式〉;而 Java注解只能通过属性名来指定属性值 。

    如果 Java 注解中的 value 属性是数组类型,那么它会变成 Kotlin 注解的 vararg属性,因此直接为它传入多个属性值即可 。

    public @interface JavaTag {
        String[] value();
    }
    

    数组类型的 value 属性会变成 Kotlin 注解的 vararg属性,因此可以在 Kotlin 程序中按如下方式使用该注解 。

    @JavaTag("kotlin", "java")
    class Book 
    

    但如果其他名称的属性是数组类型,那么在 Kotlin 中使用该注解时必须显式使用 arrayOf() 函数来构建数组。例如如下 Java注解。

    public @interface JavaTag {
        String[] infos();
    } 
    

    上面注解的 infos 属性是数组类型,因此在 Kotlin 中使用该注解时必须显式使用 arrayOf() 函数来构建数组。例如如下代码:

    @JavaTag(infos=arrayOf("kotlin", "java"))
    class Book 
    

    相关文章

      网友评论

          本文标题:注解

          本文链接:https://www.haomeiwen.com/subject/xeovtqtx.html