十、kotlin的注解和反射

1,878 阅读3分钟

先说下, kotlin的反射只使用于 kotlin 特定功能, 如果是 java 方面的功能, 最好使用java版反射

注解

java 和 kotlin 注解的区别

  1. 将类传递进注解的方式 @MyAnnotation(MyClass::class)
  2. 注解传递注解为参数的方式 去掉注解前面的 @ 就可以了, @Annotation(MyAnnotation(MyClass::class))
  3. 把数组传递到注解中, @RequestMapping(path = arrayOf("/foo", "bar")), 如果是 java 的数组注解, 将会变成 vararg 可变参数
  4. 注解传递我们的属性(类的属性), 需要属性是编译期常量的, 也就是编译期间就确定的参数, 这里我们使用 const 修饰在顶层、companion object或者object
const val TEST_TIMEOUT = 100L
@Test(timeout = TEST_TIMEOUT) fun tesMethod() {}

注解目标

kotlin 一个属性包含了很多部分, 它主要由: 字段 + get/set 函数组成, 所以注解的标注需要指定具体标注的谁

image.png

kotlin注解可以对上面几个位置进行标注

  • setparam: 对 set 函数的参数进行注解
  • set: 对属性的 set 函数注解
  • get: 对属性的 get 函数注解
  • file: 包含在文件中声明的顶层函数和顶层属性注解, 比如@file:JvmName("ClassName")对文件注解, 下面有说明
  • delegate: 为委托属性存储委托实例的字段, 注解的是属性生成出来的委托属性
  • field: 标注属性的字段, 不是幕后字段哦
  • param: 构造方法的参数
  • property: 具有此目标的注解对java不可见
  • receiver: 扩展函数或者属性的接收者

很多时候我们不知道它注解的时候哪个部位其实有一个很简单的方式, 在 kotlin 中注解, 然后在 kotlin 反编译成 java 代码的插件中查看具体注解给了哪个部位

  1. setparam注解的是构造函数的参数(记住注解的不是 name 字段)
class Demo03SetParam(@setparam:Rule var name: String) {
}
public final void setName(@Rule @NotNull String var1) {
    Intrinsics.checkNotNullParameter(var1, "<set-?>");
    this.name = var1;
}
  1. delegate 注解的是委托对象
var a: Int by MyDelegate()

委托的是一个叫 a$delegate 的委托对象

@Rule
@NotNull
private final MyDelegate a$delegate;
public final int getA() {
  return this.a$delegate.getValue(this, $$delegatedProperties[0]);
}

public final void setA(int var1) {
  this.a$delegate.setValue(this, $$delegatedProperties[0], var1);
}
  1. receiver 注解的是接收者

对扩展函数:

private fun @receiver:Rule String.f() {
   println("extFunc $this")
}

反编译:

private static final void f(@Rule String $this$f) {
   String var1 = "extFunc " + $this$f;
   boolean var2 = false;
   System.out.println(var1);
}

对扩展操作符重载:

private operator fun @receiver:Rule Demo01.plusAssign(s: String) {
   this.name = this.name + s
}

反编译:

private static final void plusAssign(@Rule Demo01 $this$plusAssign, String s) {
   Intrinsics.checkNotNullParameter($this$plusAssign, "$this$plusAssign");
   $this$plusAssign.setName($this$plusAssign.getName() + s);
}

如果是成员函数的操作符重载, 则不可以使用 @receiver:Rule , 侧面说明 成员函数没有 receiver

对扩展属性:

private var @receiver:Rule StringBuilder.c: String
   get() = this.toString()
   set(value) {
      this.append(value)
   }

反编译:

private static final String getC(@Rule StringBuilder $this$c) {
   String var10000 = $this$c.toString();
   Intrinsics.checkNotNullExpressionValue(var10000, "this.toString()");
   return var10000;
}

private static final void setC(@Rule StringBuilder $this$c, String value) {
   $this$c.append(value);
}
  1. file 目标注解

主要功能就是将注解标注目标指向文件, 这样的话可以对文件注解

前面学过的 @file:JvmName("Content")

image.png

改完直接指定了本 Demo01 文件名为 Content, 到时候在其他java文件中调用时类名字就从 Demo01 变成 Content

image.png

其他的注解就不一一做罗列了, 都比较好理解

定义注解

annotation class Rule

它不能有类体, 如果要添加属性的话

annotation class Rule(val name: String)

把它当作主构造函数就行了

kotlin的注解和java一样对 value 进行特殊处理

控制注解可以注解的目标

@Target(
   AnnotationTarget.CLASS,
   AnnotationTarget.FIELD,
   AnnotationTarget.VALUE_PARAMETER,
   AnnotationTarget.PROPERTY_SETTER,
   AnnotationTarget.FILE
)
annotation class Rule()

kotlin注解和 java注解不同, kotlin注解在运行时默认能够访问到, 所以不同显示的指定保留期

@Retention(AnnotationRetention.RUNTIME)

使用类做注解参数

annotation class Deserializeinterface(val targetClass: KClass<out Any>)

@Deserializeinterface(A::class)

使用泛型类做注解参数

annotation class CustomSerializer(val serializerClass: KClass<out ValueSerializer<*>)

反射

反射的 KClassKCallableKFuntionKProperty

JavaKotlin
ClassKClass
FieldKProperty0~KProperty2
MethodFunction
obj.getClass()obj::class 返回 KClass或者使用 obj::class.java获取Class

KClass

val kClass: KClass<Demo02> = Demo02::class

这个类可以获取很多信息, 比如 properties, 比如 functions, 比如 annotation, 比如 super 等等

:: 不能使用于局部变量

val a = 10
val property = ::a // error

这种操作符只能用于 顶层函数/属性, 扩展函数/属性 和 类中的函数/属性

我觉得 如果 java 的反射用的好, 那么 kotlin 的反射就可以不要用了, 当然除了属性或者可空类型这种 kotlin 独特的东西外, 都可以用 java 反射

KFunction

在 kotlin 中 KFunction0 ~ N, 分别表示参数的数量

0 表示函数没有参数

fun a() {}

fun main() {
   // fun a() {}
   val kFunction0: KFunction0<Unit> = ::a
}

1 表示参数只有一个

fun a(n: Int) {}

fun main() {
   // fun a() {}
   val kFunction1: KFunction1<Int, Unit> = ::a
}

2 表示参数有两个

fun a(n: Int, n2: Double) {}

fun main() {
   // fun a() {}
   val kFunction2: KFunction2<Int, Double, Unit> = ::a
}

2 然后 KFunction3<Int, Double, String> 表示第一个函数参数是 Int; 第二个函数参数是 Double; 最后一个是返回值 String

根据这个可以判断是 (1) 是一个函数
(2) 参数分别是 IntDouble
(3) 最后一个是返回值 String

如果是成员函数的话

image.png

共有 4 个泛型参数, 但是 KFunction3 却显示的是 3, 所以类的最后一个数字不能做简单的判断, 需要因地制宜

KFunction0 ~ N 你会发现不能在 kotlin 的包中发现他们, 这是合成的编译器生成类型, 主要的目的是防止 KFunction 参数的数量被认为的限制

对了在 KFunction 中你会发现 callinvoke 两个函数, 其中 callKCallable 的函数 但是他们有区别的

image.png

image.png

所以需要调用, 那最好用 invoke 调用, call 不确定类型, 最好别用

但是你也会发现

image.png

invoke 函数也不会被找到, 却可以调用

KProperty

/**
 * var b: Int = 0
 * val c: Int = 1
 */
val demo02 = Demo02()
val kMutableProperty1: KMutableProperty1<Demo02, Int> = Demo02::b
kMutableProperty1.setter.call(21)
println(kMutableProperty1.getter.invoke(demo02))

val kProperty0: KProperty0<Int> = demo02::c
// 类似于 kProperty0.getter.invoke() 或者 kProperty0.getter.call() 或者 kProperty0.getter()
println(kProperty0())

注意到了么? KMutableProperty1KProperty0 一个是 var 另一个是 val

image.png

现在我们借助 反射 获取前面注解的目标

  • setparam: 对 set 函数的参数进行注解(主构造属性的 setter 参数无法注解???)
  • set: 对属性的 set 函数注解
  • get: 对属性的 get 函数注解
  • file: 包含在文件中声明的顶层函数和顶层属性注解, 比如@file:JvmName("ClassName")对文件注解, 下面有说明
  • delegate: 为委托属性存储委托实例的字段, 注解的是属性生成出来的委托属性
  • field: 标注属性的字段, 如果注解的是属性字段的话默认可以不写field, 不是幕后字段哦
  • param: 只能标注主构造方法的参数
  • property: 具有此目标的注解对java不可见
  • receiver: 扩展函数或者属性的接收者

我们从难到易最后再读者自己写一个(反射比较重要)

有个诀窍: 编写反射的话最好打开反编译后的代码, 这样看的更加清晰, 找类 找对象都比较准确

注解目标setparam

class Person(@setparam:Rule var name: String, @param:Rule var age: Int = 22) {
   // 对 set param 注解
   @setparam:Rule
   var lastName: String = "zhazha"
}
val person = Person("zzz")
val property0 = person::name
// 主构造参数无法获取setter参数的注解
property0.setter.valueParameters.forEach { println(it.annotations) } // []
property0.setter.parameters.forEach { println(it.annotations) } // []
println("")
// 非主构造函数可以使用 kotlin 获取 setter 参数注解
val property01 = person::lastName
property01.setter.valueParameters.forEach { println(it.annotations) } // [@reflection16.Rule()]
property01.setter.parameters.forEach { println(it.annotations) } // [@reflection16.Rule()]
// 使用 原生 java 反射获取 setter 参数的注解
val clazz = person::class.java
val property1 = Person::name
val method = clazz.getDeclaredMethod("setName", property1.javaField!!.type)
method.parameters.find { it.type == property1.javaField!!.type }?.annotations?.forEach { println(it) }

这里发现 kotlin 反射的局限, 主构造函数参数的setter参数注解无法获取, 但是非主构造函数的setter参数却可以获取

如果在 次构造函数中使用 @setparam 则会报错

'@setparam:' annotations could be applied only to property declarations

'@setparam:' 注释只能应用于属性声明

注解目标delegate

该注解最终注解的是委托对象

class Person() {
   @delegate:Rule
   var delegate: Int by MyDelegate()
}

反编译的话会看到:

@Rule
@NotNull
private final MyDelegate delegate$delegate;

public final int getDelegate() {
   return this.delegate$delegate.getValue(this, $$delegatedProperties[0]);
}

public final void setDelegate(int var1) {
   this.delegate$delegate.setValue(this, $$delegatedProperties[0], var1);
}

它的 get/set 没有注解, 注解的是 delegate$delegate 对象, 而前面的 var delegate 属性变成了 getDelegatesetDelegate 的两个函数

反射获取注解源码:

    // kotlin 获取委托对象的注解
// val person = Person("1")
// val property0 = person::delegate
// val javaField = property0.javaField!!
// javaField.annotations.forEach { println(it) }
   
   // java获取委托对象注解
   val person1 = Person("2")
   val clazz = Person::class.java
   val field = clazz.declaredFields.find { it.type == MyDelegate::class.java }!!
   field.annotations.forEach { println(it) }

记住了, 使用 kotlin 获取的 KMutableProperty 实际上控制的是 kotlin 的属性, 而 kotlin 的属性包括 字段+get/set 一字段俩函数, 所以看到 val javaField = property0.javaField!! 这段代码的时候需要注意, 这是 字段+set/get 的字段, 同时该字段也叫 java 字段罢了

注解目标 receiver

private val @receiver:Rule Person.fullName: String
   get() = "${this.name}${this.lastName} age: ${this.age}"

fun main() {
/**
 * 使用 java 反射方式获取 扩展属性 fullName
 */
    val clazz = Class.forName("reflection16.Demo02Kt")
// println(clazz) // class reflection16.Demo02Kt
   val method = clazz.getDeclaredMethod("getFullName", Person::class.java)
   method.trySetAccessible()
// val person = Person("haha")
   // 这里的 method 其实是 fullName 扩展属性的 getFullName 函数
// val fullName = method.invoke(person, person) as String
// println(fullName)
   // 在这里已经获取了 Rule 注解了
   method.parameters.find { it.type == Person::class.java }!!.annotations.forEach { println(it) }
   
   /**
    * 使用 kotlin 反射方式获取
    */
// val person = Person("zhazha", 23)
// val kProperty0 = person::fullName
// val javaGetter = kProperty0.javaGetter!!
// javaGetter.parameters[0].annotations.forEach { println(it) }
}

java那么复杂的反射怎么是怎么写的??? 看 image.png 这不是有的抄么?