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 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::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
。