其实一直想写一篇详细介绍 Kotlin 反射的文章,但问题就在于,现阶段的 Kotlin 反射还真不如直接用 Java 反射来的愉快。
你问我原因?那我们就来简单说说。
本文结论基于 Kotlin 1.1.51,相信在未来的版本,本文提到的问题都将被一一解决。
1 一个 2.5M 大小的 jar 包
Java 反射直接内置在 Java 标准库当中,而 Kotlin 的反射需要单独引入,原因也很简单,Kotlin 反射库居然有 2.5M。如果不混淆,这 2.5M 的 class 文件都将最终打包到你的应用程序中,如果恰好你的程序对于体积还比较敏感,那么这将是一个值得考虑的问题。比如 Android,我们做个简单的实验,引用了反射库编译 debug 包之后大小如下:
如果不带反射包,如下:
我们看到 classes.dex 的下载大小前后差到 0.6M,当然混淆了之后会小一些。
我们再看下同样的工程,在简单的引用了某一个类的 member
之后(这样做目的是混淆之后反射包不至于被剔除掉)进行混淆,包含反射包的大小是多少:
不包含反射包的混淆后大小为:
尽管混淆之后整体体积会小一些,但 Kotlin 的反射包造成的影响却不会小很多。
2 不支持的 built-in Kotlin types
如果你尝试用 Kotlin 反射访问下 String
,你会发现 Boom,你的代码 crash 了。这是怎么回事呢?
String::class.memberFunctions
错误信息很明确的说了,内置的 Kotlin 类型暂时没有被完全支持。
代码语言:javascript复制Exception in thread "main" kotlin.reflect.jvm.internal.KotlinReflectionInternalError:
Reflection on built-in Kotlin types is not yet fully supported.
No metadata found for public open val length:
kotlin.Int defined in kotlin.String[DeserializedPropertyDescriptor@1018bde2]
-
那么什么是 Kotlin 内置的类型呢?
在 Kotlin 当中,存在不少并非真实存在,而是编译期映射的类型,例如
kotlin.Int
等数值类型,实际上是映射到了 Java 虚拟机类型中的基本类型和装箱类型;kotlin.collections.Set
等集合类型,或通过编译实现映射,或直接通过类型别名映射,也都对应到了 Java 虚拟机类型中的集合框架。这样的类型,我们就可以认为是 Kotlin 的内置类型。 -
那么既然是不完全支持,那么哪些类型有上述问题呢?
String
、Map
、Set
、Array
等这些类都会触发上述问题。 - 如何针对这些类使用反射呢? 考虑到这些类比较特殊,都是 Java 的原生类型,在 Kotlin 反射尚不能完全支持之前,建议使用 Java 反射。
3 还没来得及优化的性能
曾经在 Kotlin 的官方论坛上面看到有开发者抱怨 Kotlin 反射 API 耗时比 Java 反射长,官方开发者给出的答复是:目前在 Kotlin 反射框架上还没有花太多精力进行性能优化。
那么 Kotlin 反射究竟有多慢呢?我们对比下 Java 反射和 Kotlin 反射访问属性、修改属性、调用方法、构造对象以及前面提到的获取泛型参数的例子的耗时情况,如下(仅供参考):
单位:微秒 μs
构造对象 | 访问属性 | 修改属性 | 调用方法 | |
---|---|---|---|---|
Java 反射 | 12.7 | 25.2 | 12.2 | 18.8 |
Kotlin 反射 | 14938.0 | 85247.5 | 1316.7 | 326.3 |
以上数据在我的 MacBook Pro 上面运行所得,结果因设备有差异,但我们可以看到,Java 反射基本在 μs 级别,而 Kotlin 反射基本耗时在 ms 级别。
4 小结
整体来看,Kotlin 反射仍处于一个不太成熟的阶段,无论从体积还是从性能上考虑,现阶段使用 Kotlin 反射都应该保持一种谨慎的态度。当然,这不应该成为你排斥 Kotlin 的理由,毕竟 Kotlin 标准库已经非常成熟,并且绝大多数开发者是用不到反射的。