反射

反射允许程序在运行时检查类、函数、属性、构造函数和注解等结构。Kotlin 的函数和属性是一等概念,因此 Kotlin 反射不只是 Java 反射的包装,它还提供了 KClassKFunctionKProperty 等 Kotlin 语义。

JVM 依赖

在 JVM 上,Kotlin 反射功能位于单独的 kotlin-reflect 依赖中。这样不使用反射的应用可以避免额外体积。

Gradle Kotlin DSL:

dependencies {
    implementation(kotlin("reflect"))
}

Maven:

<dependency>
  <groupId>org.jetbrains.kotlin</groupId>
  <artifactId>kotlin-reflect</artifactId>
</dependency>

如果只用 SomeClass::class 做简单类引用,未必需要完整反射库;但要枚举成员、读取注解、调用函数等,通常需要 kotlin-reflect

类引用:KClass

val klass = User::class
println(klass.simpleName)

对象实例的运行时类:

val user: Any = User("Ada")
println(user::class.qualifiedName)

JVM 上,Kotlin class reference 和 Java class reference 不完全一样:

val kClass = User::class
val javaClass = User::class.java

Java 的 Class<?> 转 Kotlin KClass

val kClass = javaClass.kotlin

函数引用

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

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

::isOdd 可以作为函数类型 (Int) -> Boolean 使用,也可以作为 KFunction 参与反射。

成员函数引用:

val lengthFunction = String::length

更常见的是方法引用:

val names = users.map(User::name)

这里 User::name 是属性引用,不是函数引用。

属性引用

data class User(val name: String, val age: Int)

val property = User::name
println(property.get(User("Ada", 36)))

顶层属性:

val appName = "Demo"

fun main() {
    println(::appName.name)
    println(::appName.get())
}

可变属性引用:

var counter = 0

fun main() {
    ::counter.set(10)
    println(::counter.get())
}

构造函数引用

data class User(val name: String)

val factory = ::User
val user = factory("Ada")

这在依赖注入、工厂注册、测试数据构建中可能有用。

绑定引用

普通引用还需要接收者:

val unbound = String::uppercase
println(unbound("kotlin"))

绑定引用固定了接收者:

val text = "kotlin"
val bound = text::uppercase
println(bound())

读取注解

annotation class Route(val path: String)

@Route("/users")
class UserController

fun main() {
    val route = UserController::class.annotations
        .filterIsInstance<Route>()
        .firstOrNull()

    println(route?.path)
}

注意:只有运行时保留的注解才能通过反射读取:

@Retention(AnnotationRetention.RUNTIME)
annotation class RuntimeAnnotation

Kotlin 反射 vs Java 反射

Java 反射:

Class<?> clazz = User.class;
Method method = clazz.getDeclaredMethod("getName");

Kotlin 反射:

val clazz = User::class
val properties = clazz.members

Kotlin 反射更理解 Kotlin 概念,例如:

  • 属性。
  • 主构造函数。
  • 可空性。
  • 参数名。
  • Kotlin 注解目标。

Java 反射更贴近 JVM 字节码,例如字段、方法、构造器。与 Java 框架互操作时,两者可能都需要理解。

反射的代价

反射很强,但不是免费:

  • 增加运行时依赖体积。
  • 运行时访问比直接调用慢。
  • 更难被编译器检查。
  • 混淆、裁剪、Native 镜像、Android R8/ProGuard 场景需要额外配置。

业务代码能用普通类型、接口、多态或代码生成解决时,不要优先用反射。

常见使用场景

适合:

  • 序列化和反序列化框架。
  • 依赖注入容器。
  • ORM 和映射框架。
  • 测试工具。
  • 通用配置绑定。
  • 插件系统。

不适合:

  • 普通业务分支判断。
  • 绕过可见性设计。
  • 为了少写几行代码动态调用方法。

实践建议

  • JVM 项目使用完整 Kotlin 反射前确认已加入 kotlin-reflect
  • 只需要 Java 框架识别时,可能 Java 反射和注解 target 更重要。
  • 运行时读取注解时确认 AnnotationRetention.RUNTIME
  • Android 和 Native 场景谨慎使用反射,关注体积和裁剪配置。
  • 公共 API 设计优先用类型系统表达,不要把反射当成默认扩展点。

参考

  • 官方反射文档:https://kotlinlang.org/docs/reflection.html