kotlin元编程(一)一文理解 Kotlin 反射

反射是指程序可以分析、修改自身数据结构的能力。反射为静态语言补充了动态能力,使得我们可以通过类名、函数名、字段名获得操作类、函数、字段的对象,进而实现对这些程序结构的访问和修改。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。也就是说javaclass文件会保存继承的父类或者接口的泛型信息。如下图所示:

需要注意,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 是否在源代码中标记为可空类型,即这个类型的后面有没有"?"修饰。

参考

相关推荐
fatiaozhang95272 小时前
创维智能融合终端SK-M424_S905L3芯片_2+8G_安卓9_线刷固件包
android·电视盒子·刷机固件·机顶盒刷机
来来走走3 小时前
Flutter开发 了解Scaffold
android·开发语言·flutter
哆啦A梦的口袋呀4 小时前
Android 底层实现基础
android
闻道且行之5 小时前
Android Studio下载及安装配置
android·ide·android studio
alexhilton5 小时前
初探Compose中的着色器RuntimeShader
android·kotlin·android jetpack
小墙程序员5 小时前
kotlin元编程(二)使用 Kotlin 来生成源代码
android·kotlin·android studio
fatiaozhang95276 小时前
创维智能融合终端DT741_移动版_S905L3芯片_安卓9_线刷固件包
android·电视盒子·刷机固件·机顶盒刷机
KotlinKUG贵州8 小时前
贪心算法:从“瞎蒙”到稳赚
算法·kotlin
小林学Android8 小时前
Android四大组件之Activity详解
android