Kotlin 常见的元注解有四个:
- @Target,这个注解是指定了被修饰的注解都可以用在什么地方,也就是目标;
- @Retention,这个注解是指定了被修饰的注解是不是编译后可见、是不是运行时可见,也就是保留位置;
- @Repeatable,这个注解是允许我们在同一个地方,多次使用相同的被修饰的注解,使用场景比较少;
- @MustBeDocumented,指定被修饰的注解应该包含在生成的 API 文档中显示,这个注解一般用于 SDK 当中。 ```kotlin
public enum class AnnotationTarget { // 类、接口、object、注解类 CLASS, // 注解类 ANNOTATION_CLASS, // 泛型参数 TYPE_PARAMETER, // 属性 PROPERTY, // 字段、幕后字段 FIELD, // 局部变量 LOCAL_VARIABLE, // 函数参数 VALUE_PARAMETER, // 构造器 CONSTRUCTOR, // 函数 FUNCTION, // 属性的getter PROPERTY_GETTER, // 属性的setter PROPERTY_SETTER, // 类型 TYPE, // 表达式 EXPRESSION, // 文件 FILE, // 类型别名 TYPEALIAS }
public enum class AnnotationRetention { // 注解只存在于源代码,编译后不可见 SOURCE, // 注解编译后可见,运行时不可见 BINARY, // 编译后可见,运行时可见 RUNTIME }
@Deprecated 只能作用于这些地方:类、 函数、 属性、注解类、构造器、属性 getter、属性 setter、类型别名。此外,@Deprecated 这个类当中还包含了几个成员:message 代表了废弃的提示信息;replaceWith 代表了应该用什么来替代废弃的部分;level 代表警告的程度,分别是 WARNING、ERROR、HIDDEN。
```kotlin
@Deprecated(
message = "use MultipleAdapter",
replaceWith = ReplaceWith("MultipleAdapter"),
level = DeprecationLevel.WARNING
)
反射
古人云:吾日三省吾身,指的是人的自我反省能力。
反射,则是程序自我反省的能力。人的自我反省的能力,跟程序的反射,它们之间有许多相似之处。人类可以反省自己当前的状态,比如说,我们随时可以知道自己是不是困了。
而在 Kotlin 当中,程序可以通过反射来检查代码自身的状态,比如说,判断某个变量,它是不是可变的。
另外,人类反省自己的状态以后,还可以主动改变自己的状态。比如说,困了就休息一会儿、饿了就吃饭、渴了就喝点水。
而在 Kotlin 当中,我们可以在运行时,用反射来查看变量的值是否符合预期,如果不符合预期,我们就可以动态修改这个变量的值,即使这个变量是 private 的甚至是 final 的。
还有,人类可以根据状态作出不同的决策。比如说,上班的路上,如果快迟到了,我们就会走快点,如果时间很充足,就可以走慢一点。
而在程序世界里,JSON 解析经常会用到 @SerializedName 这个注解,如果属性有 @SerializedName 修饰的话,它就以指定的名称为准,如果没有,那就直接使用属性的名称来解析。
Kotlin 反射具备这三个特质:
- 感知程序的状态,包含程序的运行状态,还有源代码结构;
- 修改程序的状态;
- 根据程序的状态,调整自身的决策行为。
感知程序的状态
data class Student(val name: String, val score: Double, val height: Int)
data class School(val name: String, val address: String)
fun readMembers(obj: Any) {
//读取Object所有成员和属性名
obj::class.memberProperties.forEach {
println("${it.name}=${it.getter.call(obj)}")
}
}
fun main() {
val student = Student("job", 11.0, 111)
val school = School("shanghai", "shanghai")
readMembers(student)
readMembers(school)
}
obj::class,这是 Kotlin 反射的语法,我们叫做类引用,通过这样的语法,我们就可以读取一个变量的“类型信息”,并且就能拿到这个变量的类型,它的类型是 KClass。
public actual interface KClass<T : Any> : KDeclarationContainer, KAnnotatedElement, KClassifier {
public actual val simpleName: String?
public actual val qualifiedName: String?
override val members: Collection<KCallable<*>>
// 省略部分代码
}
要获取类的所有成员属性,我们访问它的扩展属性 memberProperties 就可以了
val <T : Any> KClass<T>.memberProperties: Collection<KProperty1<T, *>>
get() = (this as KClassImpl<T>).data().allNonStaticMembers.filter { it.isNotExtension && it is KProperty1<*, *> } as Collection<KProperty1<T, *>>
修改程序的状态
如果传入的参数当中,存在 String 类型的 address 变量,我们就将其改为 China
data class Student(val name: String, val score: Double, val height: Int)
data class School(val name: String, var address: String)
fun readMembers(obj: Any) {
//读取Object所有成员和属性名
obj::class.memberProperties.forEach {
println("${it.name}=${it.getter.call(obj)}")
}
}
fun main() {
val student = Student("job", 11.0, 111)
val school = School("shanghai", "shanghai")
readMembers(student)
readMembers(school)
modifyAddressMembers(school)
readMembers(school)
}
/**
* 修改自身的address属性
*/
fun modifyAddressMembers(obj: Any) {
obj::class.memberProperties.forEach {
if (it.name == "address" && // 判断属性名称是否是address
it is KMutableProperty1 && // 判断属性是否是可变的var修饰
it.setter.parameters.size == 2 && // 判断setter参数的个数:一个是自身,一个是实际的值
it.getter.returnType.classifier == String::class //判断getter 是不是String类型
) {
it.setter.call(obj,"China")
}
}
}
运行后的结果:
height=111
name=job
score=11.0
address=shanghai
name=shanghai
//修改自身状态
address=China
name=shanghai
根据状态,做出不同的决策
如果传入的参数没有符合需求的 address 属性,我们就输出一行错误日志。这其实也就代表了根据程序的状态,作出不同的行为。
fun modifyAddressMembers(obj: Any) {
obj::class.memberProperties.forEach {
if (it.name == "address" && // 判断属性名称是否是address
it is KMutableProperty1 && // 判断属性是否是可变的var修饰
it.setter.parameters.size == 2 && // 判断setter参数的个数:一个是自身,一个是实际的值
it.getter.returnType.classifier == String::class //判断getter 是不是String类型
) {
it.setter.call(obj,"China")
println("==== Address Change")
}else {
println("===== Wrong Type")
}
}
}
Kotlin 反射的几个关键类:KClass,KCallable,KParameter,KType
KClass 代表了一个 Kotlin 的类,下面是它的重要成员:
- simpleName,类的名称,对于匿名内部类,则为 null;
- qualifiedName,完整的类名;
- members,所有成员属性和方法,类型是Collection
>; - constructors,类的所有构造函数,类型是Collection
>>; - nestedClasses,类的所有嵌套类,类型是Collection
>; - visibility,类的可见性,类型是KVisibility?,分别是这几种情况,PUBLIC、PROTECTED、INTERNAL、PRIVATE;
- isFinal,是不是 final;
- isOpen,是不是 open;
- isAbstract,是不是抽象的;
- isSealed,是不是密封的;
- isData,是不是数据类;
- isInner,是不是内部类;
- isCompanion,是不是伴生对象;
- isFun,是不是函数式接口;
- isValue,是不是 Value Class。
KCallable 代表了 Kotlin 当中的所有可调用的元素,比如函数、属性、甚至是构造函数。下面是 KCallable 的重要成员:
- name,名称,这个很好理解,属性和函数都有名称;
- parameters,所有的参数,类型是List
,指的是调用这个元素所需的所有参数; - returnType,返回值类型,类型是 KType;
- typeParameters,所有的类型参数 (比如泛型),类型是List
; - call(),KCallable 对应的调用方法,在前面的例子中,我们就调用过 setter、getter 的 call() 方法。
- visibility,可见性;
- isSuspend,是不是挂起函数。
KParameter,代表了KCallable当中的参数,它的重要成员如下:
- index,参数的位置,下标从 0 开始;
- name,参数的名称,源码当中参数的名称;
- type,参数的类型,类型是 KType;
- kind,参数的种类,对应三种情况:INSTANCE 是对象实例、EXTENSION_RECEIVER 是扩展接受者、VALUE 是实际的参数值。
KType,代表了 Kotlin 当中的类型,它重要的成员如下:
- classifier,类型对应的 Kotlin 类,即 KClass,我们前面的例子中,就是用的 classifier == String::class 来判断它是不是 String 类型的;
- arguments,类型的类型参数,看起来好像有点绕,其实它就是这个类型的泛型参数;
- isMarkedNullable,是否在源代码中标记为可空类型,即这个类型的后面有没有“?”修饰。
所以,归根结底,反射,其实就是 Kotlin 为我们开发者提供的一个工具,通过这个工具,我们可以让程序在运行的时候“自我反省”。这里的“自我反省”一共有三种情况,其实跟我们的现实生活类似。
- 第一种情况,程序在运行的时候,可以通过反射来查看自身的状态。
- 第二种情况,程序在运行的时候,可以修改自身的状态。
- 第三种情况,程序在运行的时候,可以根据自身的状态调整自身的行为。
注解:其实就是程序代码的一种补充。 反射:其实就是程序代码自我反省的一种方式。