十、kotlin的注解和反射

12 篇文章 2 订阅
11 篇文章 1 订阅

theme: Chinese-red


先说下, 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
这不是有的抄么?

kotlin使用动态代理类的坑

前置代码:

class Book(
   val name: String
) {
}
interface Library {
   fun sell()
}
class SunLibrary(
   private val book: Book
) : Library {
   
   override fun sell() {
      println("卖书 ${book.name}")
   }
}

在 java 中我们这么编写动态代理类:

public class ProxyInvocationHandler implements InvocationHandler {
   
   private final Object target;
   
   public ProxyInvocationHandlerJava(Object target) {
      this.target = target;
   }
   
   public Object getProxy() {
      return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
   }
   
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      return method.invoke(target,args);
   }
}

如果直接使用 idea 的 java 转 kotlin 功能将生成如下代码:

class ProxyInvocationHandler(private val target: Any) : InvocationHandler {
   val proxy: Any
      get() = Proxy.newProxyInstance(this.javaClass.classLoader, target.javaClass.interfaces, this)
   
   @Throws(Throwable::class)
   override fun invoke(proxy: Any, method: Method, args: Array<Any>): Any {
      return method.invoke(target, *args)
   }
}

但是这种方式会报错:

Exception in thread "main" java.lang.NullPointerException: Parameter specified as non-null is null: method com.zhazha.test.proxy.ProxyInvocationHandler.invoke, parameter args
	at com.zhazha.test.proxy.ProxyInvocationHandler.invoke(ProxyInvocationHandler.kt)
	at com.sun.proxy.$Proxy0.sell(Unknown Source)
	at com.zhazha.test.proxy.ProxyTestKt.main(ProxyTest.kt:7)
	at com.zhazha.test.proxy.ProxyTestKt.main(ProxyTest.kt)

空指针?

注意这段: NullPointerException: Parameter specified as non-null is null: method com.zhazha.test.proxy.ProxyInvocationHandler.invoke, parameter args

很多不想看英文的看到这行代码报错:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TRdyAF7g-1656299875625)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/327420f32084419aa62bd39cd1676b0d~tplv-k3u1fbpfcp-watermark.image?)]

然后以为是这边哪里函数参数或者返回值有空指针什么的。。。

结果忽视的最重要的信息

如果仔细看异常调用栈都能发现问题

parameter args at com.zhazha.test.proxy.ProxyInvocationHandler.invoke(ProxyInvocationHandler.kt)

明显底层是 invoke 方法报错了

也就是这个函数:

@Throws(Throwable::class)
override fun invoke(proxy: Any, method: Method, args: Array<Any>): Any {
   return method.invoke(target, *args)
}

而错误提示直指错误原因:

NullPointerException: Parameter specified as non-null is null: method com.zhazha.test.proxy.ProxyInvocationHandler.invoke, parameter args

参数 args空的,但实际被指定为 non-null 参数

需要将代码改成下面这样:

@Throws(Throwable::class)
override fun invoke(proxy: Any, method: Method, args: Array<Any>?): Any {
   return method.invoke(target, *args!!)
}

结果发现又报错了

Exception in thread "main" java.lang.NullPointerException
	at com.zhazha.test.proxy.ProxyInvocationHandler.invoke(ProxyInvocationHandler.kt:13)
	at com.sun.proxy.$Proxy0.sell(Unknown Source)
	at com.zhazha.test.proxy.ProxyTestKt.main(ProxyTest.kt:7)
	at com.zhazha.test.proxy.ProxyTestKt.main(ProxyTest.kt)
return method.invoke(target, *args!!)

这行报的错误

这里需要回到前面第四章节的内容 :

image.png

所以我们可以这样:

@Throws(Throwable::class)
override fun invoke(proxy: Any, method: Method, args: Array<Any>?): Any {
   return method.invoke(target, *(args ?: arrayOf()))
}

或者这样:

return method.invoke(target, *(args ?: emptyArray()))

但发现还是报错:

image.png

啊这? 巨坑好吧,说好的 kotlin 兼容 java 的么?可以无缝切换的呢?

这里如果不清楚为什么报错可以这样改代码:

@Throws(Throwable::class)
override fun invoke(proxy: Any, method: Method, args: Array<Any>?): Any {
   val any = method.invoke(target, *(args ?: emptyArray()))
   println(any)
   return any
}

你会发现 any 输出是 null, 为空了, 也是就是 invoke 调用的返回值为空,确实,前面的 sell 函数没有返回什么变量,只能是 null

但是 invoke 的返回类型却是 Any 不能为 null

如果直接改成 any!! 就变成 null!! 又直接报错直接给你抛异常

含泪,改代码:把 invoke 返回值类型 Any 改成 Any? 完事

这里为什么可以改的原因是:InvocationHandler 接口的 invokejava 类,kotlin使用 java 类默认时,参数和返回值都会被认为介于 可空不可空 之间

之后就不会报错了

这里还有一个坑

InvocationHandlerpublic Object invoke(Object obj, Object... args) 函数的 参数 args 是可变函数

java 中 数组和可变参数是一个东西:

image.png

但是在 kotlin 中不是

image.png

*args 变成 args ,虽然 IDE 没有报错,但是在运行时报错了

Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at com.zhazha.test.proxy.ProxyInvocationHandler.invoke(ProxyInvocationHandler.kt:13)
	at com.sun.proxy.$Proxy0.sell(Unknown Source)
	at com.zhazha.test.proxy.ProxyTestKt.main(ProxyTest.kt:7)
	at com.zhazha.test.proxy.ProxyTestKt.main(ProxyTest.kt)

报参数数量错误。。。 emmm

真的恶心

报错,但是在运行时报错了

Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at com.zhazha.test.proxy.ProxyInvocationHandler.invoke(ProxyInvocationHandler.kt:13)
	at com.sun.proxy.$Proxy0.sell(Unknown Source)
	at com.zhazha.test.proxy.ProxyTestKt.main(ProxyTest.kt:7)
	at com.zhazha.test.proxy.ProxyTestKt.main(ProxyTest.kt)

报参数数量错误。。。 emmm

真的恶心

kotlin 的数组真的是败笔

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值