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
| Class | Purpose |
|---|---|
KClass<T> | Class metadata |
KFunction<R> | Function reference |
KProperty<R> | Property reference |
KParameter | Function/constructor parameter |
KType | Type information |
Common operations:
::class- get KClass::functionName- function reference::propertyName- property referencecall()- invoke dynamicallycallBy()- invoke with named args
Best practices:
- Cache reflection results khi dùng nhiều lần
- Dùng
isAccessible = truecho private members - Prefer compile-time references khi possible
- Cân nhắc performance overhead
Last updated on