2. Kotlin反射核心API:KProperty、KFunction、KParameter、KType的详细用法
反射这东西,说白了就是让代码在运行时能“看”到自己。我刚开始接触Kotlin反射时,觉得它跟Java反射差不多,后来踩了几个坑才发现——嗯,Kotlin的反射设计得更优雅,但也更讲究套路。
今天咱们就把四个核心API掰开揉碎:KProperty、KFunction、KParameter、KType。你想想看,搞懂它们,注解处理器、依赖注入、序列化这些高级玩法,你就有了底气。
2.1 KProperty:属性的“身份证”
KProperty 代表一个属性。在Java里你要用 Field 来搞,但Kotlin的属性比字段更“高级”——它自带getter/setter,还能携带注解。
我个人习惯用 KProperty 来做字段级别的注解处理。比如写一个自定义的 @BindView 注解,运行时通过反射拿到所有带这个注解的属性,然后自动赋值。
// 定义一个简单的类
data class User(val name: String, var age: Int)
fun inspectProperty() {
val prop = User::name // 获取KProperty
println("属性名: ${prop.name}") // name
println("返回类型: ${prop.returnType}") // kotlin.String
println("是否常量: ${prop.isConst}") // false
// 对于var属性,可以用KMutableProperty
val ageProp = User::age as KMutableProperty<Int>
val user = User("Alice", 25)
ageProp.setter.call(user, 30)
println(user.age) // 30
}
关键区别:
KProperty对应 val(只读)KMutableProperty对应 var(可读写)- 通过
getter/setter可以拿到对应的KFunction
我的经验: 在Android的ViewModel中,我用KProperty配合LiveData做自动绑定。给属性加个 @Observable 注解,反射遍历所有属性,自动生成观察代码。省掉了手写一堆 observe 的重复劳动。
2.2 KFunction:函数的“遥控器”
KFunction 代表一个函数。你可以通过它动态调用函数、获取参数信息、甚至修改函数的访问权限。
我曾经在写一个AOP框架时,需要拦截所有带 @Log 注解的函数。用 KFunction 配合 call() 方法,在调用前后插入日志逻辑,比Java的 Method.invoke() 更安全——因为Kotlin的反射会保留类型信息。
fun greet(name: String, age: Int): String {
return "Hello, $name! You are $age years old."
}
fun callFunction() {
val func = ::greet // 获取KFunction
println("函数名: ${func.name}") // greet
println("参数数量: ${func.parameters.size}") // 2
// 调用函数
val result = func.call("Bob", 28)
println(result) // Hello, Bob! You are 28 years old.
// 更安全的调用方式:按参数名传值
val result2 = func.callBy(mapOf(
func.parameters[0] to "Charlie",
func.parameters[1] to 35
))
println(result2) // Hello, Charlie! You are 35 years old.
}
注意: call() 方法如果参数类型不匹配,会在运行时抛异常。我个人建议优先用 callBy(),它更安全,而且支持默认参数值。
2.3 KParameter:参数的“说明书”
KParameter 描述函数或构造函数的参数。它告诉你参数叫什么名字、是什么类型、是不是可选的(有没有默认值)。
为什么这个重要?你想想看,在写依赖注入框架时,你需要知道构造函数每个参数的类型,才能决定注入什么对象。我早期用Java反射时,参数名默认是 arg0、arg1……那叫一个痛苦。Kotlin的 KParameter 直接保留了参数名,舒服多了。
class Person(val name: String, val age: Int = 18)
fun inspectParameters() {
val constructor = ::Person // 获取构造函数引用
val params = constructor.parameters
params.forEach { param ->
println("参数名: ${param.name}") // name / age
println("参数类型: ${param.type}") // kotlin.String / kotlin.Int
println("是否可选: ${param.isOptional}") // false / true
println("参数索引: ${param.index}") // 0 / 1
println("---")
}
// 利用参数信息动态构造对象
val person = constructor.callBy(mapOf(
params[0] to "David",
// params[1] 有默认值,可以不传
))
println(person) // Person(name=David, age=18)
}
实用技巧: isOptional 属性非常有用。在写Builder模式或工厂模式时,你可以根据参数是否有默认值来决定是否必须提供该参数。我曾经用这个特性做了一个“智能Builder”,自动跳过有默认值的参数。
2.4 KType:类型的“放大镜”
KType 是Kotlin反射中最“高级”的概念。它不仅仅描述一个类型,还能描述泛型参数、可空性、类型投影等Kotlin特有的类型信息。
Java的 Type 接口处理泛型时非常麻烦,尤其是嵌套泛型。Kotlin的 KType 就清晰多了——它用 arguments 属性直接暴露泛型参数,用 isMarkedNullable 判断可空性。
fun <T> inspectType(list: List<T>) {
val type = list::class.createType() // 获取KType
println("类型: ${type.classifier}") // class kotlin.collections.List
// 获取泛型参数
val arg = type.arguments.first()
println("泛型参数: ${arg.type}") // 实际类型(取决于T)
println("泛型方差: ${arg.variance}") // INVARIANT / IN / OUT
}
// 测试
inspectType(listOf("a", "b", "c"))
// 输出:
// 类型: class kotlin.collections.List
// 泛型参数: kotlin.String
// 泛型方差: INVARIANT
避坑指南: 我曾经在解析泛型时踩过一个坑——KType 的 classifier 可能返回 KClass 或 TypeParameter。如果是泛型参数(比如 T),classifier 是一个 TypeParameter 实例,不是具体的类。记得用 is TypeParameter 做判断。
2.5 四个API的协作实战
光说理论没意思,咱们来个综合例子。假设你要写一个通用的“对象转Map”工具,把数据类的属性名和值提取出来:
fun <T : Any> toMap(obj: T): Map<String, Any?> {
val kClass = obj::class
val result = mutableMapOf<String, Any?>()
// 遍历所有属性
kClass.memberProperties.forEach { prop ->
// prop 是 KProperty
val value = prop.getter.call(obj)
val type = prop.returnType // KType
// 如果属性是集合类型,递归处理
if (type.classifier == List::class) {
val list = value as? List<*>
result[prop.name] = list?.map {
if (it != null && it::class.isData) toMap(it) else it
}
} else {
result[prop.name] = value
}
}
return result
}
// 使用
data class Address(val city: String, val zip: String)
data class Employee(val name: String, val address: Address)
val emp = Employee("Eve", Address("Beijing", "100000"))
val map = toMap(emp)
println(map)
// {name=Eve, address={city=Beijing, zip=100000}}
核心思路:
KClass.memberProperties拿到所有属性(KProperty)prop.getter.call(obj)获取属性值(KFunction)prop.returnType判断类型(KType)- 递归时用
KParameter检查构造函数参数
你看,这四个API不是孤立的。在实际项目中,它们经常一起出现。我个人写注解处理器时,最常用的组合就是:KClass → memberProperties → KProperty → returnType → KType,一条链走下来,信息全拿到了。
性能提醒: 反射有开销。在Android主线程频繁调用反射,可能会引起卡顿。我建议把反射结果缓存起来,比如用 ConcurrentHashMap 缓存 KClass 或 KFunction 的引用。另外,ProGuard/R8 混淆时记得保留反射相关的类和成员。
好了,这一章的内容就到这里。四个API的用法你心里应该有数了。下一章咱们会深入注解处理器的实战——到时候这些反射知识全都会用上。
公众号:蓝海资料掘金营,微信deep3321