1. Kotlin反射基础:KClass与Class对象的获取、Kotlin反射与Java反射的对比、反射的基本操作
反射这东西,说白了就是让程序在运行时能「偷看」自己的结构。我刚开始学Kotlin时,觉得反射挺玄乎的——代码还能看代码自己?后来做框架开发多了,才发现这玩意儿是真香。今天咱们就聊聊Kotlin反射的入门三板斧。
1.1 KClass:Kotlin反射的入口
在Java里,反射的入口是Class对象。到了Kotlin,我们有了自己的反射类型——KClass。它比Java的Class更懂Kotlin的特性,比如数据类、密封类、协程这些。
获取KClass有三种方式,我平时最常用的是第一种:
// 方式一:通过类字面值(我最常用的)
val kClass: KClass<String> = String::class
// 方式二:通过对象实例
val str = "hello"
val kClassFromObj: KClass<out String> = str::class
// 方式三:通过Java的Class对象转换
val javaClass: Class<String> = String::class.java
val kClassFromJava: KClass<String> = javaClass.kotlin
::class语法,简洁明了。如果你在写Java和Kotlin混编的项目,方式三会经常用到——从Java的Class转成KClass。
拿到KClass后,你能干的事就多了。比如看看这个类叫什么、有哪些属性、构造函数长啥样:
data class User(val name: String, val age: Int)
fun inspectClass() {
val kClass = User::class
println("类名: ${kClass.simpleName}") // User
println("完整名: ${kClass.qualifiedName}") // com.example.User
println("是否是数据类: ${kClass.isData}") // true
println("构造函数数量: ${kClass.constructors.size}")
// 遍历所有属性
kClass.memberProperties.forEach { prop ->
println("属性: ${prop.name}, 类型: ${prop.returnType}")
}
}
我在项目中遇到过一个问题:用qualifiedName获取匿名内部类的类名时,返回的是null。嗯,这点要注意,匿名类没有完整的包名路径。
1.2 Kotlin反射 vs Java反射:到底差在哪?
你可能会问:Java反射用了这么多年,Kotlin为啥还要搞一套?说白了,Kotlin的反射更懂Kotlin的语法糖。我列个表格,你一看就明白:
| 对比维度 | Java反射 | Kotlin反射 |
|---|---|---|
| 入口类型 | Class<T> |
KClass<T> |
| 属性处理 | Field,不区分可变/只读 |
KProperty 和 KMutableProperty,区分val/var |
| 空安全 | 不感知可空类型 | 通过KType.isMarkedNullable判断 |
| 数据类 | 无法直接识别 | KClass.isData 直接判断 |
| 密封类 | 无法直接识别 | KClass.isSealed 直接判断 |
| 协程支持 | 无 | 可获取挂起函数信息 |
举个例子,你想判断一个属性是val还是var:
class Person(val name: String, var age: Int)
fun checkMutability() {
val kClass = Person::class
kClass.memberProperties.forEach { prop ->
when (prop) {
is KMutableProperty<*> -> println("${prop.name} 是 var,可修改")
else -> println("${prop.name} 是 val,只读")
}
}
}
// 输出:
// name 是 val,只读
// age 是 var,可修改
Field,你得自己判断有没有setter。Kotlin反射直接告诉你这是KProperty还是KMutableProperty,省心多了。
1.3 反射的基本操作:创建对象、读写属性、调用方法
光说不练假把式。咱们直接上手,看看反射到底能干啥。
1.3.1 通过反射创建对象
data class Product(val id: Int, val name: String, val price: Double)
fun createInstance() {
val kClass = Product::class
// 方式一:使用主构造函数(推荐)
val primaryCtor = kClass.primaryConstructor
val product1 = primaryCtor?.call(1001, "笔记本电脑", 5999.0)
println(product1) // Product(id=1001, name=笔记本电脑, price=5999.0)
// 方式二:使用任意构造函数
val allCtors = kClass.constructors
val product2 = allCtors.first().call(1002, "鼠标", 99.0)
println(product2)
}
我记得有一次写一个通用对象工厂,需要根据配置动态创建不同类型的对象。用primaryConstructor配合call方法,几行代码就搞定了。不过要注意——参数顺序必须和构造函数定义一致,否则会抛IllegalArgumentException。
1.3.2 读写属性
fun accessProperties() {
val obj = Product(1003, "键盘", 299.0)
val kClass = obj::class
// 读取属性
val nameProp = kClass.memberProperties.first { it.name == "name" }
val name = nameProp.get(obj)
println("商品名称: $name") // 商品名称: 键盘
// 修改属性(需要是KMutableProperty)
val priceProp = kClass.memberProperties.first { it.name == "price" }
if (priceProp is KMutableProperty<*>) {
(priceProp as KMutableProperty<Any?>).setter.call(obj, 259.0)
println("修改后: $obj") // Product(id=1003, name=键盘, price=259.0)
}
}
get()和setter.call()时,如果属性是private的,默认会抛异常。你需要调用prop.isAccessible = true来绕过访问限制。不过我个人建议——能不用反射访问私有属性就别用,容易破坏封装性。
1.3.3 调用方法
class Calculator {
fun add(a: Int, b: Int): Int = a + b
private fun secretFormula(): String = "这是秘密"
}
fun invokeMethod() {
val obj = Calculator()
val kClass = obj::class
// 调用公开方法
val addMethod = kClass.memberFunctions.first { it.name == "add" }
val result = addMethod.call(obj, 3, 5)
println("3 + 5 = $result") // 3 + 5 = 8
// 调用私有方法(需要设置可访问)
val secretMethod = kClass.memberFunctions.first { it.name == "secretFormula" }
secretMethod.isAccessible = true
val secret = secretMethod.call(obj)
println("秘密: $secret") // 秘密: 这是秘密
}
我曾经在写一个单元测试框架时,需要调用被测试类的私有方法来做验证。用isAccessible = true确实能绕过限制,但后来我反思了一下——更好的做法是让方法包级可见或者用@VisibleForTesting注解标记。反射调用私有方法,说白了是「暴力破解」,能不用就别用。
1.4 避坑指南:反射的性能与安全
反射虽好,但别滥用。我总结了几条经验:
- 性能开销:反射调用比直接调用慢一个数量级。如果你在循环里频繁用反射,建议缓存
KClass、KFunction等对象,别每次都重新获取。 - 类型安全:反射绕过了编译期类型检查。你传错参数类型,编译时不会报错,运行时才炸。所以反射代码一定要写单元测试。
- ProGuard/R8混淆:发布Release包时,反射调用的类、方法名可能被混淆掉。记得在混淆规则里保留它们。
好了,这一章的内容就到这。Kotlin反射的基础操作其实不难,关键是理解KClass这个入口,以及它和Java反射的差异。下一章咱们会深入聊聊KProperty和KFunction的更多玩法,到时候见。