Kotlin反射遇到的细节问题记录

前言

最近在做公司的项目的时候,经常会用到Kotlin和Java混合开发。官方对于Kotlin的介绍上,说的是可以和Java互相调用完美兼容。但在实际使用的时候,还是有一些瑕疵在里面,这篇文章主要是记录遇到的一些问题以及如何处理。

记录

我想要达到的效果输入一段JSON,里面包含着方法名和每个参数的信息。然后根据反射的方式调用到具体的接口实现上。(有点类似远程调用的协议)
类似这样一段JSON:

1
2
3
4
5
6
7
8
{
"name": "methodName",
"args": {
"name":"jcr",
"age":24
},
}
args为方法的参数列表,以KV的形式记录,

方法的定义:

1
2
3
4
fun testMethod(
@Param("age") age: Int,
@Param("name") name: String
): String

前面JSON的解析的操作较为简单就不说了。我们要完成这个功能,映射方法名也是通过注解来的,这里就不做过多赘述了。我们直接来说说如何映射到参数上吧,
在上方定义了testMethod这么一个方法,我们需要通过反射来获取@Param注解中的值。
首先来看@Param注解的定义:

1
2
3
4
5
6
7
8
9
10
11
12
@Retention(value = RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
@Documented
public @interface Param {

/**
* 参数名称
*
* @return
*/
String value();
}

Java反射注解的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static String[] getMethodParamsName(Method method) {
String[] names = new String[method.getParameterAnnotations().length];
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++) {
Annotation[] parameterAnnotation = parameterAnnotations[i];
for (Annotation annotation : parameterAnnotation) {
if (annotation instanceof Param) {
names[i] = ((Param) annotation).value();
}
}
}
return names;
}

Kotlin的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun getMethodParamsName(method: Method): Array<String> {
val names = Array(method.parameterAnnotations.size) { "" }
val parameterAnnotations =
method.parameterAnnotations
for (i in parameterAnnotations.indices) {
val parameterAnnotation = parameterAnnotations[i]
for (annotation in parameterAnnotation) {
if (annotation is Param) {
names[i] = annotation.value
}
}
}
return names
}

拿到注解的值后,我们就需要进行映射了。将最初的JSON串中的args下name和age的值拿出来。通过反射调用方法。
这里实现的想法是:
首先遍历参数列表,然后根据注解上的值与JSON中的args的键值对,进行判断,如果args下的key和参数上的@Param注解的值相等,则给这个参数附上相应的值。
实现代码:

1
2
3
4
5
6
args.forEach{
getMethodParamsName(method).forEach{
// if...
}
}

但在实现的时候,因为参数类型是可以变化的,不确定,所以在反序列化JSON的时候,无法确定类型,就会导致无法反序列化。所以这里,就考虑将方法的类型class(method.parameterTypes)获取出来,传入Gson,根据类型反序列化。
实现代码:

1
2
val type = TypeToken.getParameterized(CommandParam::class.java, paramType).type
return gson.fromJson(it, type)

这样写了在测试过后发现,只完成了一半。因为如果参数的类型是Kotlin的基础数据类型,交给gson反序列化的时候会报错。非基础数据类型就达成了效果。
那么为什么Kotlin的基础数据类型就不支持呢?
于是我尝试了使用Java的数据类型,即将接口的定义改为:

1
2
3
4
fun testMethod(
@Param("age") age: Integer,
@Param("name") name: String
): String

这样就可以正常工作了。于是我下断点调试了,发现Kotlin的Int和Java的Integer是两个不同的Class。
Int的ClassName为:”Int”
Integer的ClassName为:”java.lang.Integer”
在平时的Kotlin和Java的互相调用时,编译器会将Int和Integer、int当成一种类型,在传参、回调、泛型等场景都可以满足。
而在反射里却不是这样了。Int和Integer、int是完全不同的Class。所以要满足上面的需求,就需要手动添加区分他们的代码。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fun kotlinClassConvert(clazz: Class<*>): Class<*> {
when (clazz) {
Int::class.java ->
return Integer::class.java
Boolean::class.java ->
return java.lang.Boolean::class.java
Float::class.java ->
return java.lang.Float::class.java
Double::class.java ->
return java.lang.Double::class.java
Byte::class.java ->
return java.lang.Byte::class.java
Char::class.java ->
return java.lang.Character::class.java
Short::class.java ->
return java.lang.Short::class.java
Long::class.java ->
return java.lang.Long::class.java
else ->
return clazz
}
}

将Kotlin基础数据类型全部转换为Java的之后,上面的问题也就得到了解决。