运行时的反射
本文为《深入实践 kotlin 元编程》读书笔记,想要深入了解 kotlin 元编程的同学可以阅读原书。
反射是指程序可以分析、修改自身数据结构的能力。
反射为静态语言提供了动态能力的补充,使得我们可以通过类名、函数名、字段名就可以获得操作类、函数、字段的对象,进而实现对这些程序结构的访问和修改。反射广泛应用于数据序 列化、软件测试等场景当中。
Java 反射
Java 反射是专门设计用来支持 Java 运行时元编程的特性的。
基本功能
Java 1.1 引入反射,提供了绝大多数我们现在可以用到的反射 API,包括 Class、 Method、Field 这些核心类型以及其成员,使得我们可以使用反射来实现几乎所有通过编码来 实现的程序功能。
解除访问限制
Java 1.2 中引入了 AccessibleObject 作为 Method 和 Field 等类型的父类(这 些类在 Java 1.2 之前直接继承自 Object 类)。如此一来,我们就可以通过 setAccessible 来解除 Java 类的私有成员的访问限制。这次更新使得反射的能力得到了 “史诗级”增强,也使得开发者可以在任意位置访问几乎任意类型的私有成员。
动态代理
Java 1.3 引入了 Proxy 类型,支持了运行时动态代理。将动态代理与反射配合使用, 我们可以在运行时动态实现 Java 接口,使程序的灵活性得到大大地提升。动态代理可以用于 实现 AOP(面向切面编程)、模拟接口、拦截方法调用等用途。
对注解的支持
Java 5 引入了注解,同时也在反射中增加了处理注解的 API。注解可以为源代码提供元信息,我们既可以在编译时解析注解,也可以在运行时使用反射访问注解。
对方法参数名的支持
Java 8 以前,Java 编译器不会将 Java 方法的参数名编译到 JVM 字节码当中,因而 我们也无法使用反射访问到参数的名字。对于这样的场景,广大框架设计者常用的办法就是使用 注解将参数名添加到方法参数上,在运行时读取注解的值来获取参数名。
Java 8 为编译器增加了参数 -parameters,以及 Parameter 类来获取方法参数名。
在这种情况下,我们无须借助注解也可以读取到方法的参数名。
访问 Kotlin 代码
在 Kotlin JVM 程序中,可以使用 Java 反射来访问 Kotlin 类型。只不过 Java 反 射对 Kotlin 的语法特性一无所知,它只能以 Java 的视角来观察 Kotlin 语法,因此使用 Java 反射访问 Kotlin 的类型及其成员时,本质上是在访问 Kotlin 代码编译生成的字节码所对应的 Java 类型。
Kotlin 反射
Kotlin 反射是专门为 Kotlin 的语法特性设计实现的运行时元编程技术,它的出现解决了 Java 反射无法识别 Kotlin 语法特性的问题。
基本功能
Kotlin 反射的设计与 Java 反射如出一辙。KClass 在公共标准库中的定义非常简单,只有三个成员。Kotlin JS 和 Kotlin Native 当中的实现也是如此,这意味着 Kotlin 在除了 JVM 以外的所有平台上提供的反射能力都极其有限。
也正是因为如此,通常我们提到 Kotlin 反射,都是特指 Kotlin JVM 上的反射。
属性引用和函数引用
属性引用和函数引用可以分别通过类名::属性和类名::函数名的语法获取,其中类名部分在属性和函数是顶级声明的情况下缺省。
通过类名获取的引用是没有绑定 receiver 的,因此在调用时需要传入对应类型的实例。
我们也可以通过实例直接获取引用,语法为实例::属性名和实例::函数名,通过实例获取的引用是绑定了 receiver 的引用。
typeOf
typeOf 是一个函数,调用时,它会返回具体化(reified)之后的类型 T 对应的 KType 实例。
有读者可能会有疑问,T 不是已经具体化了吗?我们为什么不直接使用 T::class 来获取 KClass 实例呢?显然,KClass 和 KType 还是有很大的差异的,前者是泛型擦除之后的运行时类型表示,后者则包含了泛型信息,可以用于序列化或者反序列化等场景。
typeOf 主要应对的就是需要获取 KType 或者在 JVM 上获取 Type 的场景, 也经常与 inline 和 reified 一起使用来编写库函数。