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 }

  1. @Deprecated 只能作用于这些地方:类、 函数、 属性、注解类、构造器、属性 getter、属性 setter、类型别名。此外,@Deprecated 这个类当中还包含了几个成员:message 代表了废弃的提示信息;replaceWith 代表了应该用什么来替代废弃的部分;level 代表警告的程度,分别是 WARNINGERRORHIDDEN
  2. ```kotlin
  3. @Deprecated(
  4. message = "use MultipleAdapter",
  5. replaceWith = ReplaceWith("MultipleAdapter"),
  6. level = DeprecationLevel.WARNING
  7. )

反射

古人云:吾日三省吾身,指的是人的自我反省能力。
反射,则是程序自我反省的能力。人的自我反省的能力,跟程序的反射,它们之间有许多相似之处。人类可以反省自己当前的状态,比如说,我们随时可以知道自己是不是困了。
而在 Kotlin 当中,程序可以通过反射来检查代码自身的状态,比如说,判断某个变量,它是不是可变的。
另外,人类反省自己的状态以后,还可以主动改变自己的状态。比如说,困了就休息一会儿、饿了就吃饭、渴了就喝点水。
而在 Kotlin 当中,我们可以在运行时,用反射来查看变量的值是否符合预期,如果不符合预期,我们就可以动态修改这个变量的值,即使这个变量是 private 的甚至是 final 的。
还有,人类可以根据状态作出不同的决策。比如说,上班的路上,如果快迟到了,我们就会走快点,如果时间很充足,就可以走慢一点。
而在程序世界里,JSON 解析经常会用到 @SerializedName 这个注解,如果属性有 @SerializedName 修饰的话,它就以指定的名称为准,如果没有,那就直接使用属性的名称来解析。
Kotlin 反射具备这三个特质:

  • 感知程序的状态,包含程序的运行状态,还有源代码结构;
  • 修改程序的状态;
  • 根据程序的状态,调整自身的决策行为。

感知程序的状态

  1. data class Student(val name: String, val score: Double, val height: Int)
  2. data class School(val name: String, val address: String)
  3. fun readMembers(obj: Any) {
  4. //读取Object所有成员和属性名
  5. obj::class.memberProperties.forEach {
  6. println("${it.name}=${it.getter.call(obj)}")
  7. }
  8. }
  9. fun main() {
  10. val student = Student("job", 11.0, 111)
  11. val school = School("shanghai", "shanghai")
  12. readMembers(student)
  13. readMembers(school)
  14. }

obj::class,这是 Kotlin 反射的语法,我们叫做类引用,通过这样的语法,我们就可以读取一个变量的“类型信息”,并且就能拿到这个变量的类型,它的类型是 KClass。

  1. public actual interface KClass<T : Any> : KDeclarationContainer, KAnnotatedElement, KClassifier {
  2. public actual val simpleName: String?
  3. public actual val qualifiedName: String?
  4. override val members: Collection<KCallable<*>>
  5. // 省略部分代码
  6. }

要获取类的所有成员属性,我们访问它的扩展属性 memberProperties 就可以了

  1. val <T : Any> KClass<T>.memberProperties: Collection<KProperty1<T, *>>
  2. get() = (this as KClassImpl<T>).data().allNonStaticMembers.filter { it.isNotExtension && it is KProperty1<*, *> } as Collection<KProperty1<T, *>>

修改程序的状态

如果传入的参数当中,存在 String 类型的 address 变量,我们就将其改为 China

  1. data class Student(val name: String, val score: Double, val height: Int)
  2. data class School(val name: String, var address: String)
  3. fun readMembers(obj: Any) {
  4. //读取Object所有成员和属性名
  5. obj::class.memberProperties.forEach {
  6. println("${it.name}=${it.getter.call(obj)}")
  7. }
  8. }
  9. fun main() {
  10. val student = Student("job", 11.0, 111)
  11. val school = School("shanghai", "shanghai")
  12. readMembers(student)
  13. readMembers(school)
  14. modifyAddressMembers(school)
  15. readMembers(school)
  16. }
  17. /**
  18. * 修改自身的address属性
  19. */
  20. fun modifyAddressMembers(obj: Any) {
  21. obj::class.memberProperties.forEach {
  22. if (it.name == "address" && // 判断属性名称是否是address
  23. it is KMutableProperty1 && // 判断属性是否是可变的var修饰
  24. it.setter.parameters.size == 2 && // 判断setter参数的个数:一个是自身,一个是实际的值
  25. it.getter.returnType.classifier == String::class //判断getter 是不是String类型
  26. ) {
  27. it.setter.call(obj,"China")
  28. }
  29. }
  30. }

运行后的结果:

  1. height=111
  2. name=job
  3. score=11.0
  4. address=shanghai
  5. name=shanghai
  6. //修改自身状态
  7. address=China
  8. name=shanghai

根据状态,做出不同的决策

如果传入的参数没有符合需求的 address 属性,我们就输出一行错误日志。这其实也就代表了根据程序的状态,作出不同的行为。

  1. fun modifyAddressMembers(obj: Any) {
  2. obj::class.memberProperties.forEach {
  3. if (it.name == "address" && // 判断属性名称是否是address
  4. it is KMutableProperty1 && // 判断属性是否是可变的var修饰
  5. it.setter.parameters.size == 2 && // 判断setter参数的个数:一个是自身,一个是实际的值
  6. it.getter.returnType.classifier == String::class //判断getter 是不是String类型
  7. ) {
  8. it.setter.call(obj,"China")
  9. println("==== Address Change")
  10. }else {
  11. println("===== Wrong Type")
  12. }
  13. }
  14. }

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 为我们开发者提供的一个工具,通过这个工具,我们可以让程序在运行的时候“自我反省”。这里的“自我反省”一共有三种情况,其实跟我们的现实生活类似。

  • 第一种情况,程序在运行的时候,可以通过反射来查看自身的状态。
  • 第二种情况,程序在运行的时候,可以修改自身的状态。
  • 第三种情况,程序在运行的时候,可以根据自身的状态调整自身的行为。

注解:其实就是程序代码的一种补充。 反射:其实就是程序代码自我反省的一种方式。