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——属性的getterset——属性的setterreceiver——扩展函数或者扩展属性的接收者参数param——构造方法的参数setparam——属性setter的参数delegate——为委托属性存储委托实例的字段file——包含在文件中声明的顶层函数和属性的类
任何应用到file目标的注解都必须放在文件的顶层,放在package指令之前。
和Java不一样的是,kotlin允许你对任意的表达式应用注解,而不仅仅是类和函数的声明及类型。
kotlin提供了各种注解来控制kotlin编写的声明如何编译成字节码并暴露给Java调用者。其中一些注解代替了Java语言中对应的关键字,比如,@Volatile和@Strictfp直接充当了Java的关键字volatile和strictfp的替身。
@JvmName将改变由kotlin生成的Java方法或字段的名称@JvmStatic能被用在对象声明或者伴生对象的方法上,把它们暴露成Java的静态方法。@JvmOverloads指导kotlin编译器为带默认参数值的函数生成多个重载函数。@JvmField可以应用于一个属性,把这个属性暴露成一个没有访问器的共有Java字段
声明注解
annotation class JsonExcludeannotation 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::class,out关键字说明允许引用那些继承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全部继承了KAnnotatedElement。KClass既可以用来表示类也可以表示对象。KProperty可以表示任何属性,而它的子类KMutableProperty表示一个用var声明的可变属性。KFunciton0,KFunction1和KProperty.Getter,KMutableProperty.Setter都继承了KFunction。