10.注解与反射

一个注解允许你把额外的元数据关联到一个声明上。然后元数据可以被相关工具的源代码工具访问,通过编译好的类文件或是在运行时,取决于这个注解是如何配置的。

注解只能拥有如下类型的参数:基本数据类型、字符串、枚举、类引用、其他的注解类,以及前面这些类型的数组。指定注解实参的语法与Java有些微小的差别:

  • 要把一个类指定为注解实参,在类名后加上::class@MyAnnotation(MyClass::class)
  • 要把另一个注解指定为一个实参,去掉注解名称前面的@,@Deprecated("use removeAt(index) instead.", ReplaceWith("removeAt(index)"))
  • 要把一个数组指定为一个实参,使用arrayOf函数。@RequestMapping(path = arrayOf("/foo", "/bar"))

注解实参需要在编译期就是已知的,所以不能引用任意的属性作为实参。要把属性当做注解实参使用,需要使用const修饰符标记它,来告知编译器这个属性是编译期常量。

用const标注的属性可以声明在一个文件的顶层或者一个object之中,而且必须初始化为基本数据类型或者String类型的值。

许多情况下,kotlin源码中的单个声明会对应成多个Java声明,而且它们每个都能携带注解。例如,一个kotlin属性就对应了一个Java字段、一个getter、以及一个潜在的setter和它的参数。

使用点目标声明被用来说明要注解的元素。使用点目标被放在@符号和注解名称之间,并用冒号和注解名称隔开。@get:Rule导致注解@Rule被应用到了属性getter上。

如果你使用Java中声明的注解来注解一个属性,它会被默认地应用到相应的字段上。kotlin也可以让你声明被直接对应到属性上的注解。

kotlin支持的使用点目标的完整列表如下:

  • property——Java的注解不能应用这种使用点目标
  • field——为属性生成的字段
  • get——属性的getter
  • set——属性的setter
  • receiver——扩展函数或者扩展属性的接收者参数
  • param——构造方法的参数
  • setparam——属性setter的参数
  • delegate——为委托属性存储委托实例的字段
  • file——包含在文件中声明的顶层函数和属性的类

任何应用到file目标的注解都必须放在文件的顶层,放在package指令之前。

和Java不一样的是,kotlin允许你对任意的表达式应用注解,而不仅仅是类和函数的声明及类型。

kotlin提供了各种注解来控制kotlin编写的声明如何编译成字节码并暴露给Java调用者。其中一些注解代替了Java语言中对应的关键字,比如,@Volatile@Strictfp直接充当了Java的关键字volatilestrictfp的替身。

  • @JvmName将改变由kotlin生成的Java方法或字段的名称
  • @JvmStatic能被用在对象声明或者伴生对象的方法上,把它们暴露成Java的静态方法。
  • @JvmOverloads指导kotlin编译器为带默认参数值的函数生成多个重载函数。
  • @JvmField可以应用于一个属性,把这个属性暴露成一个没有访问器的共有Java字段

声明注解

  • annotation class JsonExclude
  • annotation class JsonName(val name : String)

Java的注解

public @interface JsonName {
    String value();
}

和Java一样,一个kotlin注解类自己也可以被注解。可以应用到注解类上的注解被称为元注解。

@Target(AnnotationTarget.ANNOTATION_CLASS,AnnotationTarget.PROPERTY, AnnotationTarget.FIELD)
annotation class SolarexAnnotaion

注意,在Java代码中无法使用目标为PROPERTY的注解;要让这样的注解可以在Java中使用,可以给它添加第二个目标AnnotationTarget.FIELD,这样注解既可以应用到kotlin中的属性上,也可以应用到Java中的字段上。

@Retention注解被用来说明你声明的注解是否会存储到.class文件,以及在运行时是否可以通过反射来访问它。Java默认会在.class文件中保留注解但不会让它们在运行时被访问到。大多数注解确实需要在运行时存在,所以kotlin的默认行为不同:注解拥有RUNTIME保留期。

使用类做注解参数annotation class DeserializeInterface(val targetClass : KClass<out Any>)

如果只写出KClass<Any>而没有用out修饰符,就不能传递CompanyImpl::class作为实参,唯一允许的实参将是Any::classout关键字说明允许引用那些继承Any的类,而不仅仅是引用Any自己。

Kotlin反射API的主要入口是KClass,它代表了一个类。KClass对应的是java.lang.Class

MyClass::class的写法会带给你一个KClass的实例,要在运行时获取一个对象的类,首先使用javaClass属性获得它的Java类,这直接等价于Java中的java.lang.Object.getClass(),然后访问该类的.kotlin扩展属性,从Java切换到Kotlin的反射API。

KCallable是函数和属性的超接口,它声明了call方法,允许你调用对应的函数或者对应属性的getter。

var d = 42
>>> val kdp = ::d
    kdp.setter.call(56)
    println(d)
val person = Person("Solarex", 18)
val memberProperty = Person::age
println(memberProperty.get(person))

KFunction接口和KProperty接口都继承了KCallable,它提供了一个通用的call方法。

KCallable.callBy方法能用来调用带默认参数值的方法。

因为所有的声明都能被注解,所以代表运行时声明的接口,比如KClass,KCallable,KParameter全部继承了KAnnotatedElementKClass既可以用来表示类也可以表示对象。KProperty可以表示任何属性,而它的子类KMutableProperty表示一个用var声明的可变属性。KFunciton0,KFunction1KProperty.GetterKMutableProperty.Setter都继承了KFunction

results matching ""

    No results matching ""