theme: Chinese-red
先说下, kotlin的反射只使用于 kotlin 特定功能, 如果是 java 方面的功能, 最好使用java版反射
注解
java 和 kotlin 注解的区别
- 将类传递进注解的方式
@MyAnnotation(MyClass::class)
- 注解传递注解为参数的方式 去掉注解前面的
@
就可以了,@Annotation(MyAnnotation(MyClass::class))
- 把数组传递到注解中,
@RequestMapping(path = arrayOf("/foo", "bar"))
, 如果是 java 的数组注解, 将会变成vararg
可变参数 - 注解传递我们的属性(类的属性), 需要属性是
编译期常量
的, 也就是编译期间就确定的参数, 这里我们使用const
修饰在顶层、companion object
或者object
const val TEST_TIMEOUT = 100L
@Test(timeout = TEST_TIMEOUT) fun tesMethod() {}
注解目标
kotlin 一个属性包含了很多部分, 它主要由: 字段 + get/set 函数组成, 所以注解的标注需要指定具体标注的谁
kotlin注解可以对上面几个位置进行标注
setparam
: 对set
函数的参数进行注解set
: 对属性的set
函数注解get
: 对属性的get
函数注解file
: 包含在文件中声明的顶层函数和顶层属性注解, 比如@file:JvmName("ClassName")
对文件注解, 下面有说明delegate
: 为委托属性存储委托实例的字段, 注解的是属性生成出来的委托属性field
: 标注属性的字段, 不是幕后字段哦param
: 构造方法的参数property
: 具有此目标的注解对java不可见receiver
: 扩展函数或者属性的接收者
很多时候我们不知道它注解的时候哪个部位其实有一个很简单的方式, 在 kotlin 中注解, 然后在 kotlin 反编译成 java 代码的插件中查看具体注解给了哪个部位
setparam
注解的是构造函数的参数(记住注解的不是 name 字段)
class Demo03SetParam(@setparam:Rule var name: String) {
}
public final void setName(@Rule @NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.name = var1;
}
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);
}
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);
}
file
目标注解
主要功能就是将注解标注目标指向文件, 这样的话可以对文件注解
前面学过的 @file:JvmName("Content")
改完直接指定了本 Demo01
文件名为 Content
, 到时候在其他java文件中调用时类名字就从 Demo01
变成 Content
了
其他的注解就不一一做罗列了, 都比较好理解
定义注解
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<*>)
反射
反射的 KClass
、KCallable
、KFuntion
和 KProperty
Java | Kotlin |
---|---|
Class | KClass |
Field | KProperty0~KProperty2 |
Method | Function |
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 中 KFunction
从 0 ~ 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) 参数分别是 Int
和 Double
(3) 最后一个是返回值 String
如果是成员函数的话
共有 4
个泛型参数, 但是 KFunction3
却显示的是 3
, 所以类的最后一个数字不能做简单的判断, 需要因地制宜
KFunction0 ~ N
你会发现不能在 kotlin 的包中发现他们, 这是合成的编译器生成类型, 主要的目的是防止KFunction
参数的数量被认为的限制
对了在 KFunction
中你会发现 call
和 invoke
两个函数, 其中 call
是 KCallable
的函数 但是他们有区别的
所以需要调用, 那最好用 invoke
调用, call
不确定类型, 最好别用
但是你也会发现
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())
注意到了么? KMutableProperty1
和 KProperty0
一个是 var
另一个是 val
现在我们借助 反射 获取前面注解的目标
- 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
属性变成了 getDelegate
和 setDelegate
的两个函数
反射获取注解源码:
// 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那么复杂的反射怎么是怎么写的??? 看
这不是有的抄么?
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!!)
这行报的错误
这里需要回到前面第四章节的内容 :
所以我们可以这样:
@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()))
但发现还是报错:
啊这? 巨坑好吧,说好的 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
接口的 invoke
是 java
类,kotlin
使用 java
类默认时,参数和返回值都会被认为介于 可空
和 不可空
之间
之后就不会报错了
这里还有一个坑
InvocationHandler
的 public Object invoke(Object obj, Object... args)
函数的 参数 args
是可变函数
在 java
中 数组和可变参数是一个东西:
但是在 kotlin
中不是
从 *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 的数组真的是败笔