反射是指程序可以分析、修改自身数据结构的能力。反射为静态语言补充了动态能力,使得我们可以通过类名、函数名、字段名获得操作类、函数、字段的对象,进而实现对这些程序结构的访问和修改。Kotlin 支持两种反射,一种是 Java 反射,一种是 Kotlin 的反射。
Java 的反射
由于 Kotlin 与 Java 兼容,因此 Kotlin 也可以使用 Java 的反射。下面介绍一下java反射的使用。
反射调用方法
arduino
// 获取私有方法(需要 setAccessible(true))
val privateMethod = javaClass.getDeclaredMethod("privateMethod")
privateMethod.isAccessible = true // 突破可见性限制
println(privateMethod.invoke(MyClass())) // 输出: Private
在 Java 1.2 中,支持设置 isAccessible
来访问 Java 类的私有成员。
反射获取方法的信息
kotlin
clazz.methods
.forEach { method ->
// 判断是否是静态方法
val isStatic = Modifier.isStatic(method.modifiers)
// 获取参数列表
val parameterTypes: Array<Class<*>?> = method.parameterTypes
val parameterList = Arrays.stream<Class<*>?>(parameterTypes)
.map<String?> { obj: Class<*>? -> obj!!.getName() }
.collect(Collectors.joining(", "))
// 获取返回值类型
val returnType: Class<*> = method.returnType
val returnTypeName = returnType.getName()
println("isStatic: $isStatic parameterList: $parameterList returnTypeName: $returnTypeName")
}
需要注意:在Java 8以前,Java编译器不会将Java方法的参数名编译到JVM字节码中,因而我们也无法使用反射访问参数的名字。比如 Retrofit 就是使用注解来设置变量名的。Java 8为编译器增加了参数
-parameters
以及Parameter
类来获取方法参数名
获取注解信息
Java 5引入了注解,同时在反射中增加了处理注解的API。如下代码示例所示。
kotlin
// 定义注解
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class MyAnnotation(val value: String)
class MyClass {
@MyAnnotation("Test")
fun annotatedMethod() {}
}
fun main() {
val method = MyClass::class.java.getMethod("annotatedMethod")
val annotation = method.getAnnotation(MyAnnotation::class.java)
println(annotation?.value) // 输出: Test
}
获取泛型信息
在 Java 中,反射可以帮助我们获取到泛型的相关信息。如下所示:
typescript
public class MyClass {
private final Map<String, Integer> map = Map.of("a", 1, "b", 2);
public List<Pair<String, Integer>> getList() {
return List.of();
}
}
获取代码如下:
ini
public class MyClass {
private final Map<String, Integer> map = Map.of("a", 1, "b", 2);
public List<Pair<String, Integer>> getList() {
return List.of();
}
public void check(List<String> value) {
}
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
// 获取 map 字段的泛型信息
Field field = MyTClass.class.getDeclaredField("map");
ParameterizedType genericType = (ParameterizedType) field.getGenericType();
Type keyType = genericType.getActualTypeArguments()[0];
Type valueType = genericType.getActualTypeArguments()[1];
System.out.printf("Key: %s, Value: %s%n", keyType, valueType);
// 获取 getList() 方法返回值的泛型信息
Method getListMethod = MyTClass.class.getMethod("getList");
ParameterizedType returnType = (ParameterizedType) getListMethod.getGenericReturnType();
Type listTypeArgument = returnType.getActualTypeArguments()[0];
System.out.println(listTypeArgument);
// 获取 check() 方法参数的泛型信息
for (Method method : MyTClass.class.getMethods()) {
if ("check".equals(method.getName())) {
Parameter[] parameters = method.getParameters();
if (parameters.length > 0) {
ParameterizedType paramType = (ParameterizedType) parameters[0].getParameterizedType();
Type paramTypeArgument = paramType.getActualTypeArguments()[0];
System.out.println(paramTypeArgument);
}
}
}
}
我们都知道在 Java 中泛型信息会在编译阶段被删除,但是我们为什么还可以获取这个信息呢?这是因为声明侧的泛型信息在 class 文件以 signature
的形式被保存在 Constant pool
中。这就是Java反射能获取到泛型信息的原因。
但是下面这种定义泛型的方式,是无法通过上述方式来获取传入的参数泛型类型的。
php
public <T> void function(List<T> value) {
}
那么如何解决这个问题呢?在 Gson 中,是通过定义一个 TypeToken
来实现获取传入的泛型类型。如下所示:
typescript
// Gson 常用的情况
public List<String> parse(String jsonStr){
List<String> topNews = new Gson().fromJson(jsonStr, new TypeToken<List<String>>() {}.getType());
return topNews;
}
其原理是:Class
类提供了一个方法public Type getGenericSuperclass()
,可以获取到带泛型信息的父类Type
。也就是说java
的class
文件会保存继承的父类或者接口的泛型信息。如下图所示:

需要注意,Class
类暂时没有提供方法来获取当前的类的泛型信息,因此只能通过父类获取。示例如下:
csharp
System.out.println("class = " + ArrayList.class); // 输出 class = class java.util.ArrayList
System.out.println("class = " + ArrayList<String>.class); // 不支持
System.out.println("class = " + (new ArrayList<String>()).getClass()); // 输出 class = class java.util.ArrayList
kotlin 的反射
由于 Kotlin 还包含 Kotlin JS 和 Kotlin Native,因此 Kotlin 的公众标准库提供的反射能力非常有限。只有在 JVM 平台上,kotlin 才提供完整的反射功能,为此你需要添加如下依赖。
arduino
implementation "org.jetbrains.kotlin:kotlin-reflect:1.9.10"
Kotlin JVM 反射是专门为Kotlin的语法特性设计实现的运行时元编程技术,它的出现解决了Java反射无法识别Kotlin语法特性的问题。
Kotlin 提供了很多Java不支持的特性,这些特性很难直接使用JVM字节码进行等价描述。为了确保发布的二进制产物当中包含完整的语法信息,Kotlin编译器会为每一个类生成一个
@Metadata
注解,也会为模块内所有的顶级声明(Top-Level Declaration)生成一个模块专属的元数据文件,这些文件通常以kotlin_module
为后缀。因此可以说 Kotlin 反射本质上是读取@Metadata注解的值来还原Kotlin的原始声明信息
kotlin 反射的基本使用如下所示:
获取类的信息
kotlin
class Person(val name: String, var age: Int)
fun main() {
val kClass = Person::class
// 获取类名
println("类名: ${kClass.simpleName}") // 输出: Person
// 获取所有属性
val properties = kClass.members.filterIsInstance<KProperty<*>>()
println("属性: ${properties.joinToString { it.name }}") // 输出: name, age
// 获取所有方法
val methods = kClass.members.filterIsInstance<KFunction<*>>()
println("方法: ${methods.joinToString { it.name }}") // 输出: component1, component2, copy, equals, hashCode, toString 等
}
获取方法的信息
kotlin
class Calculator {
fun add(a: Int, b: Int): Int = a + b
}
fun main() {
val kClass = Calculator::class
// 获取 add 方法
val addMethod = kClass.functions.first { it.name == "add" }
// 输出方法信息
println("方法名: ${addMethod.name}") // 输出: add
println("返回类型: ${addMethod.returnType}") // 输出: kotlin.Int
println("参数列表: ${addMethod.parameters.joinToString { "${it.name}:${it.type}" }}") // 输出: a, b
// 调用方法
val result = addMethod.call(Calculator(), 1, 2)
println("调用结果: $result") // 输出: 3
}
修改属性
kotlin
val member = Member("test", "123")
println("name = ${member.name}")
member::class.memberProperties.forEach {
if(it.name == "name" //属性名是否对上
&& it is KMutableProperty1 //是否是可变的
&& it.setter.parameters.size == 2 //设置参数个数是否为2,第一个参数是 obj 自身,第二个是实际的值
&& it.getter.returnType.classifier == String::class //设置的类型是否为String
) {
it.setter.call(member, "new test")
}
}
println("name = ${member.name}")
获取注解信息
kotlin
// 定义注解
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class MyAnnotation(val value: String)
// 使用注解
@MyAnnotation("类注解")
class MyAnnotationClass {
@MyAnnotation("方法注解")
fun myMethod() {}
@MyAnnotation("属性注解")
val myProperty: Int = 42
}
fun main() {
val kClass = MyAnnotationClass::class
// 获取类上的注解
val classAnnotations = kClass.annotations
println("类注解: ${classAnnotations.filterIsInstance<MyAnnotation>().first().value}")
// 获取方法上的注解
val method = kClass.functions.first { it.name == "myMethod" }
val methodAnnotations = method.annotations
println("方法注解: ${methodAnnotations.filterIsInstance<MyAnnotation>().first().value}")
// 获取属性上的注解
val property = kClass.memberProperties.first { it.name == "myProperty" }
val propertyAnnotations = property.annotations
println("属性注解: ${propertyAnnotations.filterIsInstance<MyAnnotation>().first().value}")
// 使用 findAnnotation() 直接获取指定类型的注解:
val myAnnotation = kClass.findAnnotation<MyAnnotation>()
println(myAnnotation?.value) // 输出: 类注解
// 获取 MyAnnotation 上声明的Target元注解的值
val metaAnnotation = myAnnotation?.annotationClass?.findAnnotation<Target>()
println(metaAnnotation?.allowedTargets?.joinToString(",") { it.name }) // 输出为: CLASS,FUNCTION,PROPERTY
}
获取泛型信息
kotlin
// 获取 List<String> 的类型
val listType = typeOf<List<String>>()
// 输出类型信息
println("原始类型: ${listType.classifier}") // 输出: class kotlin.collections.List
println("泛型参数: ${listType.arguments}") // 输出: [kotlin.String]
kotlin
data class Person(val name: String, val age: Int)
@OptIn(ExperimentalStdlibApi::class)
fun main() {
val jsonStr = "[{\"name\":\"John Doe\",\"age\":30}]"
val topNews =
Gson().fromJson<List<Person>>(jsonStr, typeOf<List<Person>>().javaType)
println("parse $topNews")
}
KClass、KCallable、KParameter、KType
从上面的例子可以看到,在 Kotlin 反射中,最重要的就是KClass、KCallable、KParameter、KType 四个类。它们的重要参数分别如下表所示:
KClass
KClass代表了一个 Kotlin 的类,下面是它的重要成员:
属性 | 作用 |
---|---|
simpleName | 类的名称,对于匿名内部类,则为 null |
qualifiedName | 完整的类名 |
members | 所有成员属性和方法,类型是Collection<KCallable<*>> |
constructors | 类的所有构造函数,类型是Collection<KFunction>> |
nestedClasses | 类的所有嵌套类,类型是Collection<KClass<*>> |
visibility | 类的可见性,类型是KVisibility?,分别是这几种情况,PUBLIC、PROTECTED、INTERNAL、PRIVATE; |
isFinal | 是不是 final |
isOpen | 是不是 open |
isAbstract | 是不是抽象的 |
isSealed | 是不是密封的 |
isData | 是不是数据类 |
isInner | 是不是内部类 |
isCompanion | 是不是伴生对象 |
isFun | 是不是函数式接口 |
isValue | 是不是 Value Class |
KCallable
KCallable 代表了 Kotlin 当中的所有可调用的元素,比如函数、属性、甚至是构造函数。下面是 KCallable 的重要成员:
属性 | 作用 |
---|---|
name | 属性和函数的名称; |
parameters | 所有的参数,类型是List,指的是调用这个元素所需的所有参数; |
returnType | 返回值类型,类型是 KType; |
typeParameters | 所有的类型参数 (比如泛型),类型是List; |
call() | KCallable 对应的调用方法,在前面的例子中,我们就调用过 setter、getter 的call() 方法。 |
visibility | 可见性; |
isSuspend | 是不是挂起函数。 |
KParameter
KParameter 代表了KCallable当中的参数,它的重要成员如下:
属性 | 作用 |
---|---|
index | 参数的位置,下标从 0 开始; |
name | 参数的名称,源码当中参数的名称; |
type | 参数的类型,类型是 KType; |
kind | 参数的种类,对应三种情况:INSTANCE 是对象实例、EXTENSION_RECEIVER 是扩展接受者、VALUE 是实际的参数值。 |
- KType
KType,代表了 Kotlin 当中的类型,它重要的成员如下:
属性 | 作用 |
---|---|
classifier | 类型对应的 Kotlin 类,即 KClass,我们前面的例子中,就是用的 classifier == String::class 来判断它是不是 String 类型的; |
arguments | 泛型参数; |
isMarkedNullable | 是否在源代码中标记为可空类型,即这个类型的后面有没有"?"修饰。 |