Kotlin 反射:KClass 与 KProperty
Kotlin 提供了一套强大的反射 API,允许你在运行时检查类、函数、属性等程序结构。其中 KClass 和 KProperty 是两个核心接口,分别用于描述类和属性的元信息。掌握它们能让你在不硬编码的情况下动态访问对象结构。
获取 KClass 对象
获取一个类型的 KClass 对象是使用 Kotlin 反射的第一步。Kotlin 通过“类引用”语法实现这一点。
-
使用双冒号操作符
::class获取具体类型的KClass。例如:val stringClass: KClass<String> = String::class -
调用
javaClass.kotlin转换 Java 的Class对象为KClass(适用于从 Java 互操作场景传入的对象):val obj = "hello" val kClass = obj.javaClass.kotlin -
注意:
::class返回的是编译期已知类型的KClass;若要处理泛型或未知类型,需配合reified类型参数(仅限内联函数)。
使用 KClass 检查和创建实例
KClass 提供了多种方法来查询类的信息或创建新实例。
-
检查一个对象是否属于某个类:
if (String::class.isInstance("text")) { // 成立 } -
获取类的简单名称或完整限定名:
println(String::class.simpleName) // 输出 "String" println(String::class.qualifiedName) // 输出 "kotlin.String" -
创建类的新实例(要求类有无参构造函数且未被优化掉):
class Person(val name: String = "Unknown") val personClass = Person::class val instance = personClass.objectInstance ?: personClass.constructors.first().call()注意:如果类是
object单例,则objectInstance非空;否则需通过构造器手动调用。 -
遍历所有声明的成员属性:
data class User(val id: Int, val name: String) User::class.memberProperties.forEach { prop -> println(prop.name) } // 输出: id, name
理解 KProperty
KProperty 是 Kotlin 中表示属性的反射接口。它分为 KProperty0(无接收者)、KProperty1(单接收者)、KProperty2(双接收者),最常见的是 KProperty1<T, R>,表示类型为 T 的对象拥有返回类型为 R 的属性。
-
获取属性引用:
class Book(val title: String) val titleProp: KProperty1<Book, String> = Book::title -
读取属性值:
val book = Book("Kotlin in Action") val value = titleProp.get(book) // 等价于 book.title -
检查属性是否可变(即是否为
var):class Config(var debug: Boolean = false) val prop = Config::debug if (prop is KMutableProperty1<*, *>) { prop.set(Config(), true) // 可写入 } -
获取属性名称和返回类型:
println(titleProp.name) // "title" println(titleProp.returnType) // kotlin.String
动态访问对象属性(通用方案)
你可以编写一个通用函数,通过属性名字符串动态读取任意对象的属性值。
-
定义一个扩展函数,接受对象和属性名:
fun Any.getPropertyValue(propertyName: String): Any? { return this::class.memberProperties .firstOrNull { it.name == propertyName } ?.get(this) } -
调用该函数读取值:
data class Product(val id: Int, val price: Double) val p = Product(101, 29.99) val idValue = p.getPropertyValue("id") // 101 val priceValue = p.getPropertyValue("price") // 29.99 -
注意:此方法依赖
memberProperties,默认只包含public属性。若需访问private或继承属性,需启用kotlin-reflect并配置 JVM 参数(生产环境慎用)。
KClass 与 KProperty 的典型应用场景
| 场景 | 说明 | 使用方式 |
|---|---|---|
| 序列化/反序列化库 | 如 kotlinx.serialization 在运行时读取属性 | 通过 KClass.memberProperties 遍历字段 |
| ORM 框架映射 | 将数据库列映射到 Kotlin 属性 | 利用 KProperty.name 匹配列名 |
| 依赖注入容器 | 自动注入带注解的属性 | 检查 KProperty.annotations |
| 调试工具 | 动态打印对象所有字段 | 遍历 memberProperties 并 get() |
启用反射支持
Kotlin 反射功能依赖 kotlin-reflect 库,它不会自动包含在标准库中。
-
添加依赖(以 Gradle 为例):
implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.0") -
确认运行时类路径包含该 JAR,否则会抛出
NoClassDefFoundError。 -
注意:反射会带来性能开销和包体积增长,避免在高频路径(如循环内部)使用。
安全与限制
-
不要假设所有属性都能被反射访问。
private、internal或被 ProGuard/R8 混淆的属性可能无法获取。 -
避免在 Android 发布版中过度使用反射,因其会增加方法数并影响启动速度。
-
检查
KProperty是否为KMutableProperty再尝试写入,否则会抛出IllegalStateException。 -
泛型类型擦除:
KProperty.returnType在运行时无法保留泛型实参(如List<String>会显示为List<*>),需结合其他机制处理。
示例:通用相等性比较
利用 KClass 和 KProperty 实现一个忽略顺序的深度属性比较:
fun <T : Any> T.deepEquals(other: T): Boolean {
if (this === other) return true
if (this::class != other::class) return false
return this::class.memberProperties.all { prop ->
val value1 = prop.get(this)
val value2 = prop.get(other)
when {
value1 == null && value2 == null -> true
value1 == null || value2 == null -> false
value1 is Any && value2 is Any ->
if (value1::class.isData) value1.deepEquals(value2) else value1 == value2
else -> value1 == value2
}
}
}
使用:
data class Point(val x: Int, val y: Int)
val p1 = Point(1, 2)
val p2 = Point(1, 2)
println(p1.deepEquals(p2)) // true
暂无评论,快来抢沙发吧!