Kotlin 反射你敢用吗?

4,146 阅读3分钟
原文链接: mp.weixin.qq.com

其实一直想写一篇详细介绍 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 了。这是怎么回事呢?

  1. String::class.memberFunctions

错误信息很明确的说了,内置的 Kotlin 类型暂时没有被完全支持。

  1. Exception in thread "main" kotlin.reflect.jvm.internal.KotlinReflectionInternalError:

  2. Reflection on built-in Kotlin types is not yet fully supported.

  3. No metadata found for public open val length:

  4. kotlin.Int defined in kotlin.String[DeserializedPropertyDescriptor@1018bde2]

  • 那么什么是 Kotlin 内置的类型呢?

    在 Kotlin 当中,存在不少并非真实存在,而是编译期映射的类型,例如 kotlin.Int 等数值类型,实际上是映射到了 Java 虚拟机类型中的基本类型和装箱类型; kotlin.collections.Set 等集合类型,或通过编译实现映射,或直接通过类型别名映射,也都对应到了 Java 虚拟机类型中的集合框架。这样的类型,我们就可以认为是 Kotlin 的内置类型。

  • 那么既然是不完全支持,那么哪些类型有上述问题呢?

    StringMapSetArray 等这些类都会触发上述问题。

  • 如何针对这些类使用反射呢?

    考虑到这些类比较特殊,都是 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 标准库已经非常成熟,并且绝大多数开发者是用不到反射的。


欢迎关注微信公众号 Kotlin