Skip to Content

Reflection trong Kotlin

1. Giới thiệu

Reflection cho phép kiểm tra và thao tác với code tại runtime - xem classes, functions, properties, và gọi chúng dynamically.

2. Setup

Thêm dependency trong build.gradle.kts:

dependencies { implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.0") }

3. KClass - Class References

import kotlin.reflect.KClass import kotlin.reflect.full.* data class User(val name: String, val age: Int) fun main() { // Lấy KClass từ class val userClass: KClass<User> = User::class // Lấy KClass từ instance val user = User("Alice", 25) val classFromInstance: KClass<out User> = user::class // Basic info println("Class name: ${userClass.simpleName}") // User println("Qualified: ${userClass.qualifiedName}") // com.example.User println("Is data: ${userClass.isData}") // true println("Is abstract: ${userClass.isAbstract}") // false }

Class properties và functions

import kotlin.reflect.full.* data class Person(val name: String, var age: Int) { fun greet() = "Hello, $name!" private fun secret() = "Shhh" } fun main() { val kClass = Person::class // Properties println("=== Properties ===") kClass.memberProperties.forEach { prop -> println("${prop.name}: ${prop.returnType}") } // name: kotlin.String // age: kotlin.Int // Functions println("\n=== Functions ===") kClass.memberFunctions.forEach { func -> println("${func.name}(${func.parameters.joinToString { it.name ?: "receiver" }})") } // greet(receiver) // secret(receiver) // ... // Constructors println("\n=== Constructors ===") kClass.constructors.forEach { ctor -> println(ctor.parameters.map { "${it.name}: ${it.type}" }) } }

4. KCallable - Functions và Properties

Function references

import kotlin.reflect.KFunction fun greet(name: String): String = "Hello, $name!" fun main() { // Function reference val greetRef: KFunction<String> = ::greet // Call via reference println(greetRef.call("Alice")) // Hello, Alice! // Function info println("Name: ${greetRef.name}") println("Parameters: ${greetRef.parameters.map { it.name }}") println("Return type: ${greetRef.returnType}") }

Property references

import kotlin.reflect.KProperty import kotlin.reflect.KMutableProperty data class User(val name: String, var age: Int) fun main() { val user = User("Alice", 25) // Property reference val nameRef: KProperty<String> = User::name val ageRef: KMutableProperty<Int> = User::age // Get values println(nameRef.get(user)) // Alice println(ageRef.get(user)) // 25 // Set value (mutable only) ageRef.set(user, 30) println(user.age) // 30 // Property info println("Name: ${nameRef.name}") println("Is const: ${nameRef.isConst}") println("Is lateinit: ${nameRef.isLateinit}") }

5. Dynamic Invocation

Gọi function dynamically

import kotlin.reflect.full.* class Calculator { fun add(a: Int, b: Int): Int = a + b fun subtract(a: Int, b: Int): Int = a - b fun multiply(a: Int, b: Int): Int = a * b } fun main() { val calc = Calculator() val kClass = calc::class // Tìm function theo tên val addFunc = kClass.memberFunctions.find { it.name == "add" }!! // Gọi function val result = addFunc.call(calc, 10, 5) println("10 + 5 = $result") // 10 + 5 = 15 // Gọi function bất kỳ theo tên fun calculate(operation: String, a: Int, b: Int): Int { val func = kClass.memberFunctions.find { it.name == operation } ?: throw IllegalArgumentException("Unknown operation: $operation") return func.call(calc, a, b) as Int } println(calculate("add", 5, 3)) // 8 println(calculate("subtract", 5, 3)) // 2 println(calculate("multiply", 5, 3)) // 15 }

Set property dynamically

import kotlin.reflect.full.* import kotlin.reflect.jvm.isAccessible class Config { var host: String = "localhost" var port: Int = 8080 private var secret: String = "hidden" } fun main() { val config = Config() val kClass = config::class // Set public property val hostProp = kClass.memberProperties.find { it.name == "host" } as kotlin.reflect.KMutableProperty1<Config, String> hostProp.set(config, "example.com") println(config.host) // example.com // Access private property val secretProp = kClass.memberProperties.find { it.name == "secret" }!! secretProp.isAccessible = true println(secretProp.get(config)) // hidden }

6. Create Instances Dynamically

import kotlin.reflect.full.* data class User(val name: String, val age: Int = 0) fun main() { val kClass = User::class // Find primary constructor val constructor = kClass.primaryConstructor!! // Create with positional args val user1 = constructor.call("Alice", 25) println(user1) // User(name=Alice, age=25) // Create with named args (respects defaults) val user2 = constructor.callBy(mapOf( constructor.parameters[0] to "Bob" // age uses default value )) println(user2) // User(name=Bob, age=0) }

Factory pattern với reflection

import kotlin.reflect.KClass import kotlin.reflect.full.* interface Repository class UserRepository : Repository class ProductRepository : Repository object RepositoryFactory { private val registry = mutableMapOf<String, KClass<out Repository>>() init { register("user", UserRepository::class) register("product", ProductRepository::class) } fun register(name: String, kClass: KClass<out Repository>) { registry[name] = kClass } fun create(name: String): Repository { val kClass = registry[name] ?: throw IllegalArgumentException("Unknown repository: $name") return kClass.createInstance() } } fun main() { val userRepo = RepositoryFactory.create("user") println(userRepo::class.simpleName) // UserRepository }

7. Annotations với Reflection

import kotlin.reflect.full.* @Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.RUNTIME) annotation class JsonField(val name: String) @Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.RUNTIME) annotation class JsonIgnore data class User( @JsonField("user_id") val id: Int, @JsonField("user_name") val name: String, @JsonIgnore val password: String ) fun toJson(obj: Any): String { val kClass = obj::class val properties = kClass.memberProperties .filterNot { it.findAnnotation<JsonIgnore>() != null } .map { prop -> val jsonName = prop.findAnnotation<JsonField>()?.name ?: prop.name val value = prop.getter.call(obj) val valueStr = if (value is String) "\"$value\"" else value.toString() "\"$jsonName\": $valueStr" } return "{ ${properties.joinToString(", ")} }" } fun main() { val user = User(1, "Alice", "secret123") println(toJson(user)) // { "user_id": 1, "user_name": "Alice" } }

8. Type Information

import kotlin.reflect.full.* import kotlin.reflect.typeOf // Reified type để giữ type info at runtime inline fun <reified T> printType() { val type = typeOf<T>() println("Type: $type") println("Classifier: ${type.classifier}") println("Arguments: ${type.arguments}") println("Is nullable: ${type.isMarkedNullable}") } fun main() { printType<String>() // Type: kotlin.String // Classifier: class kotlin.String // Arguments: [] // Is nullable: false printType<List<Int>>() // Type: kotlin.collections.List<kotlin.Int> // Classifier: class kotlin.collections.List // Arguments: [kotlin.Int] printType<Map<String, List<Int>>>() // Type: kotlin.collections.Map<kotlin.String, kotlin.collections.List<kotlin.Int>> }

9. Use Cases thực tế

Simple Dependency Injection

import kotlin.reflect.KClass import kotlin.reflect.full.* @Target(AnnotationTarget.CONSTRUCTOR) annotation class Inject class Logger { fun log(message: String) = println("[LOG] $message") } class Database { fun query(sql: String) = println("[DB] $sql") } class UserService @Inject constructor( private val logger: Logger, private val database: Database ) { fun createUser(name: String) { logger.log("Creating user: $name") database.query("INSERT INTO users (name) VALUES ('$name')") } } object Container { private val instances = mutableMapOf<KClass<*>, Any>() init { register(Logger::class) { Logger() } register(Database::class) { Database() } } fun <T : Any> register(kClass: KClass<T>, factory: () -> T) { instances[kClass] = factory() } @Suppress("UNCHECKED_CAST") fun <T : Any> resolve(kClass: KClass<T>): T { return instances[kClass] as? T ?: createInstance(kClass) } private fun <T : Any> createInstance(kClass: KClass<T>): T { val constructor = kClass.constructors .find { it.findAnnotation<Inject>() != null } ?: kClass.primaryConstructor ?: throw IllegalArgumentException("No suitable constructor") val args = constructor.parameters.map { param -> resolve(param.type.classifier as KClass<*>) } return constructor.call(*args.toTypedArray()) } } fun main() { val userService = Container.resolve(UserService::class) userService.createUser("Alice") // [LOG] Creating user: Alice // [DB] INSERT INTO users (name) VALUES ('Alice') }

Object Mapping

import kotlin.reflect.full.* data class UserDto(val id: Int, val name: String, val email: String) data class UserEntity(val id: Int, val name: String, val email: String, val createdAt: Long) inline fun <reified T : Any> map(source: Any): T { val targetClass = T::class val constructor = targetClass.primaryConstructor!! val sourceProps = source::class.memberProperties.associateBy { it.name } val args = constructor.parameters.associateWith { param -> sourceProps[param.name]?.getter?.call(source) ?: if (param.isOptional) null else throw IllegalArgumentException("Missing: ${param.name}") }.filterValues { it != null } return constructor.callBy(args) } fun main() { val entity = UserEntity(1, "Alice", "alice@example.com", System.currentTimeMillis()) val dto: UserDto = map(entity) println(dto) // UserDto(id=1, name=Alice, email=alice@example.com) }

10. Performance Considerations

import kotlin.reflect.full.* data class User(val name: String, val age: Int) // ❌ Slow: reflection mỗi lần gọi fun slowGet(user: User, propName: String): Any? { return user::class.memberProperties .find { it.name == propName } ?.getter?.call(user) } // ✅ Better: cache property reference val nameProperty = User::class.memberProperties.find { it.name == "name" }!! fun fastGet(user: User): String { return nameProperty.get(user) as String } // ✅ Best: dùng property reference trực tiếp val directRef = User::name fun fastestGet(user: User): String { return directRef.get(user) }

📝 Tóm tắt

ClassPurpose
KClass<T>Class metadata
KFunction<R>Function reference
KProperty<R>Property reference
KParameterFunction/constructor parameter
KTypeType information

Common operations:

  • ::class - get KClass
  • ::functionName - function reference
  • ::propertyName - property reference
  • call() - invoke dynamically
  • callBy() - invoke with named args

Best practices:

  • Cache reflection results khi dùng nhiều lần
  • Dùng isAccessible = true cho private members
  • Prefer compile-time references khi possible
  • Cân nhắc performance overhead
Last updated on