«

Java崽学习Kotlin---反射快速入门

ZealSinger 发布于 阅读:72 Kotlin


Kotlin的反射和其协程一样,需要引入专门的反射库

implementation(kotlin("reflect"))

获取类对象

和Java一样,使用反射需要先获取到类对象即Java中的class对象,在Kotlin中通过 类名::class 的方式即可获取到某个类的类对象,在Kotlin中类对象类型不是class类而是称之为KClass<>类

// 当然也能省略类型自动推导
var clz:KClass<Student> = Student::class 

需要注意的是,KClass是Kotlin中的类对象,和Java中的class对象不是直接兼容的,如果需要使用Java中的类对象,每个KClass对象都有一个成员名为java的Java的class对象,所以可以通过 KClass.java的方式获取到Java的class对象

val clz:KClass<Student> = Student::class     
var javaClass:Class<Student> = clz.java

除此之外,KClass对象还有很多比较方便的方法,其作用如下

image-20250508221828349

调用方法

有了KClass对象,自然就能获取到KClass对象即该类中的成员方法对象,获得方法对象之后,在Java中,可以通过invoke方法执行指定对象的方法,在Ktolin中也可以实现,其关键方法是 call 方法 , function函数对象.call(实例对象,方法参数一,方法参数二....)

class Student {
    fun study(topic: String) {
        println("Studying $topic")
    }
}

fun main() {
    val clz = Student::class

    // clz.declaredFunctions 获取该类下的所有的方法集合
    // 获取方法对象(假设 study 方法存在) 获取第一个方法名为study的方法
    val studyFunction = clz.declaredFunctions.first { it.name == "study" }

    // 创建 Student 实例
    val student = clz.createInstance()

    // 调用方法:studyFunction.call(实例, 参数...)
    studyFunction.call(student, "Kotlin Reflection")
    // 输出: Studying Kotlin Reflection
}

那如果有多个同名的重载方法,可以在.first{....}的逻辑中加入一些参数个数,参数类型的判断,从而指定是哪个方法执行,案例代码如下

我们有三个参数列表不同的study重载方法,依次指定调用

class Student {
    fun study() = println("默认学习")
    fun study(topic: String) = println("学习主题:$topic")
    fun study(hours: Int) = println("学习时长:$hours 小时")
}

fun main() {
    val clz = Student::class
    val student = Student()

    // 获取所有 study 方法
    val studyFunctions = clz.declaredFunctions.filter { it.name == "study" }

    // it.parameters获取方法的参数列表,返回一个parameters[]数组,但成员方法参数数组的0号索引位置一定是所在类的实例对象 后面的开始才是参数列表
    // 例如:study(topic: String) 的参数列表为:parameters[0]: Student 实例  parameters[1]: String 类型的 topic

    // 调用无参方法
    studyFunctions.first { it.parameters.size == 1 } // it.parameters获取方法的参数列表 .size==1即只有一个参数 即paramenters[0]即实例对象
        .call(student) // 输出: 默认学习

    // 调用 String 参数方法
    studyFunctions.first { 
        it.parameters.size == 2 && 
        it.parameters[1].type == typeOf<String>()
    }.call(student, "反射") // 输出: 学习主题:反射

    // 调用 Int 参数方法
    studyFunctions.first { 
        it.parameters.size == 2 && 
        it.parameters[1].type == typeOf<Int>()
    }.call(student, 3) // 输出: 学习时长:3 小时
}

绑定的类引用

通过使用对象作为接收者,可以使用相同的::Class语法获取指定对象的类的引用,即::Class是动态的获取而不是静态的获取,在多态和继承的环境下可以体现 可以理解为Java中的getClass()方法,防止Class<?>的类型擦除

// 1. 定义父类和子类
open class Widget(val name: String)

class GoodWidget : Widget("GoodWidget") {
    fun optimize() = println("Optimizing...")
}

class BadWidget : Widget("BadWidget") {
    fun logError() = println("Error detected!")
}

fun main() {
    // 2. 声明类型为 Widget,实际实例是子类
    val widget1: Widget = GoodWidget() // 实际类型是 GoodWidget
    val widget2: Widget = BadWidget()  // 实际类型是 BadWidget

    // 3. 打印声明类型 vs 运行时类型
    println("widget1 的声明类型: ${Widget::class.qualifiedName}") // 输出 Widget
    println("widget1 的运行时类型: ${widget1::class.qualifiedName}") // 输出 GoodWidget
    println("widget2 的运行时类型: ${widget2::class.qualifiedName}") // 输出 BadWidget

    // 4. 模拟断言失败场景
    assert(widget2 is GoodWidget) { 
        "实际类型是子类: ${widget2::class.qualifiedName}" 
    }
}

可调用的引用

指向函数,成员属性,构造器的引用,可以被调用,所有的可调用的引用的共同父类/基类为KCallable,这里的泛型R是返回值的类型,对于属性而言就是属性的数据类型,对于构造器而言就是构造器的返回值的类型

函数引用

一个类中存在一个函数

fun isOdd(x):Int = x%2!=0

我们正常的调用就是通过 .isOdd(...) 的方式进行直接调用,另外一种情况就是可以将其作为一个函数类型的值传入给另外一个函数作为参数,这个时候就可以借助 :: 操作符

val numbers = listOf(1,2,3)
println(numbers.filter(::isOdd))

::isOdd 其实就是一个 (Int)->Boolean 类型的函数表达式,如果方法出现了重载,如果编译器能推断出对应的函数参数类型就可以使用,反之则不能使用

fun isOdd(x: Int) = x % 2 != 0
fun isOdd(s: String) = s == "brillig" || s == "slithy" || s == "tove"

val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd)) // 指向 isOdd(x: Int) 函数

上述的核心思想就是:可以通过函数引用和::符号,调用这个函数,也可以将整个函数作为一个函数类型的参数返回且作为一个参数参与到另外一个函数的逻辑中,可以结合Kotlin中的lambd表达式和入参可以为函数这两个特点好好理解一下,看如下案例,可以很简单的实现函数组合

fun main() {
    // 方法一 接受一个String字符串 返回其长度
    fun length(s: String) = s.length

    // 方法二 接受一个Int数据判断其是否为偶数 如果使得返回false反之返回true
    fun isOdd(x: Int) = x % 2 != 0

    // 方法三  是方法一和方法二的组合  接受两个函数类型参数f(接受一个B类型的参数返回一个C类型的结果)  和g(接受一个A类型的参数返回一个B类型的参数)
    // 返回一个 接受函数类型参数,该函数会接受一个A类型的参数返回一个C类型的结果
    // 实际上就是来两个方法的嵌套使用
    fun <A, B, C> compose(f: (B) -> C, g: (A) -> B): (A) -> C {
        return { x -> f(g(x)) }
    }

    val oddLength = compose(::isOdd, ::length)
    val strings = listOf("a", "ab", "abc")

    // 其实逻辑就是 strings遍历 每个元素先执行length得到长度 然后将返回值放入到isOdd方法中进行判断决定是否过滤掉 如果为true则过滤反之则留下
    println(strings.filter(oddLength))
}

属性引用

在Kotlin中,可以将属性作为一等对象来访问,也是使用::操作符,可以调用其get构造器和set构造器,也能获取其变量名,注解,是否对外允许继承,是否抽象等等属性都可以获取

var x: Int = 10
fun main() {
    println(::x.name)  // 获取变量x的变量名
    println(::x.get())  // 调用get方法获取变量值
    ::x.set(15)  // 调用set方法更新数据
    println(::x.get())
}

image-20250508213035020

在所有使用单参数函数的地方,都可以使用属性引用

val strs = listOf("a", "bc", "def")
// 使用属性引用
println(strs.map(String::length))

// 不使用属性应用
println(strs.map{it.length})

构造器引用

和方法和属性一样,也可以引用构造器,可以利用 ::类名 来引用一个类的无参构造,那么借助这个,可以在一些接受函数参数的函数中使用构造器引用来作为入参

fun main() {
// 用 ::Foo 标识对 Foo类的无参构造的引用
// 即传入了Foo的无参构造函数 刚好满足function函数的需要 无参构造也是没有入参返回的是一个Foo类的对象
    function(::Foo)
}
class Foo

// 接受一个 factory函数类型参数 该函数没有入参但是会返回一个Foo类结果
fun function(factory: () -> Foo) {
    val x: Foo = factory()
}

Kotlin 编程