리플렉션 & 애노테이션
KClass<T>
런타임에 클래스 정보를 나타냅니다.
// 클래스 참조 획득
val klass: KClass<String> = String::class
val klass2: KClass<out Any> = "hello"::class // 인스턴스에서
// 기본 정보
klass.simpleName // "String"
klass.qualifiedName // "kotlin.String"
klass.isAbstract // false
klass.isSealed // false
klass.isData // false
klass.objectInstance // null (object가 아니므로)
// object 싱글톤 접근
object MySingleton { val value = 42 }
MySingleton::class.objectInstance?.value // 42인스턴스 생성
data class Point(val x: Int, val y: Int)
// 기본 생성자로 인스턴스 생성
val instance = Point::class.createInstance() // 기본 생성자 필요
// 생성자 파라미터가 있는 경우
val constructor = Point::class.primaryConstructor!!
val point = constructor.call(3, 4) // Point(3, 4)
// callBy — 이름으로 파라미터 전달
val params = constructor.parameters.associateWith { param ->
when (param.name) {
"x" -> 10
"y" -> 20
else -> null
}
}
constructor.callBy(params) // Point(10, 20)KFunction
fun add(a: Int, b: Int): Int = a + b
val fn: KFunction2<Int, Int, Int> = ::add
fn.name // "add"
fn.parameters // [KParameter(a: Int), KParameter(b: Int)]
fn.returnType // kotlin.Int
fn.call(3, 4) // 7 — 타입 검사 없음 (vararg)
fn.invoke(3, 4) // 7 — 타입 안전
// isSuspend
suspend fun asyncFn() {}
::asyncFn.isSuspend // trueKProperty
data class User(val name: String, var age: Int)
val nameProp: KProperty1<User, String> = User::name
val ageProp: KMutableProperty1<User, Int> = User::age
val user = User("홍길동", 30)
nameProp.name // "name"
nameProp.get(user) // "홍길동"
ageProp.get(user) // 30
ageProp.set(user, 31) // age 변경
// 프로퍼티 메타데이터
nameProp.returnType // kotlin.String
nameProp.visibility // KVisibility.PUBLIC애노테이션 읽기
@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class Validate(val message: String = "유효하지 않음")
@Serializable
@Validate("사용자 유효성 검사 실패")
data class User(
@Validate("이름은 필수") val name: String,
val age: Int,
)
// 클래스 애노테이션 읽기
val classAnnotation = User::class.findAnnotation<Validate>()
println(classAnnotation?.message) // "사용자 유효성 검사 실패"
// 프로퍼티 애노테이션 읽기
val nameProp = User::class.memberProperties.find { it.name == "name" }
val propAnnotation = nameProp?.findAnnotation<Validate>()
println(propAnnotation?.message) // "이름은 필수"
// 모든 애노테이션
User::class.annotations.forEach { println(it) }실전 패턴
리플렉션 기반 유효성 검사
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class NotBlank(val field: String = "")
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class Min(val value: Int)
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class Max(val value: Int)
data class CreateUserRequest(
@NotBlank("이름") val name: String,
@Min(0) @Max(150) val age: Int,
@NotBlank("이메일") val email: String,
)
fun validate(obj: Any): List<String> {
val errors = mutableListOf<String>()
val klass = obj::class
klass.memberProperties.forEach { prop ->
val value = prop.getter.call(obj)
prop.findAnnotation<NotBlank>()?.let { ann ->
if (value is String && value.isBlank()) {
errors.add("${ann.field.ifBlank { prop.name }}: 비어있을 수 없습니다.")
}
}
if (value is Int) {
prop.findAnnotation<Min>()?.let { ann ->
if (value < ann.value) errors.add("${prop.name}: 최솟값은 ${ann.value}입니다.")
}
prop.findAnnotation<Max>()?.let { ann ->
if (value > ann.value) errors.add("${prop.name}: 최댓값은 ${ann.value}입니다.")
}
}
}
return errors
}
val request = CreateUserRequest("", -1, "invalid")
validate(request).forEach { println(it) }
// 이름: 비어있을 수 없습니다.
// age: 최솟값은 0입니다.리플렉션 기반 복사
fun <T : Any> shallowCopy(source: T, overrides: Map<String, Any?> = emptyMap()): T {
val klass = source::class
val constructor = klass.primaryConstructor
?: error("주 생성자 없음: ${klass.simpleName}")
val args = constructor.parameters.associateWith { param ->
if (overrides.containsKey(param.name)) {
overrides[param.name]
} else {
klass.memberProperties
.find { it.name == param.name }
?.getter?.call(source)
}
}
return constructor.callBy(args)
}
data class Config(val host: String, val port: Int, val debug: Boolean)
val original = Config("localhost", 8080, false)
val copy = shallowCopy(original, mapOf("port" to 9090))
// Config(host=localhost, port=9090, debug=false)typeOf — KType 획득
reified 없이도 타입 정보를 얻습니다.
import kotlin.reflect.typeOf
val intType = typeOf<Int>()
val listType = typeOf<List<String>>()
val mapType = typeOf<Map<String, List<Int>>>()
val nullableType = typeOf<String?>()
intType.isMarkedNullable // false
nullableType.isMarkedNullable // true
listType.arguments // [TypeProjection(String)]표준 애노테이션
// @Deprecated — 사용 중단 표시
@Deprecated(
message = "use newFunction() instead",
replaceWith = ReplaceWith("newFunction(arg)"),
level = DeprecationLevel.WARNING, // WARNING / ERROR / HIDDEN
)
fun oldFunction(arg: String) {}
// @Suppress — 경고 억제
@Suppress("UNCHECKED_CAST", "DEPRECATION")
fun legacyCode() {}
// @OptIn — 실험적 API 사용 선언
@OptIn(ExperimentalCoroutinesApi::class)
fun useExperimentalApi() {}
// @RequiresOptIn — 내 API를 실험적으로 표시
@RequiresOptIn(
message = "이 API는 실험적입니다.",
level = RequiresOptIn.Level.WARNING,
)
annotation class ExperimentalMyApi
@ExperimentalMyApi
fun experimentalFeature() {}Java 상호운용 애노테이션
class Service {
companion object {
@JvmStatic
fun create(): Service = Service() // Java: Service.create()
@JvmField
val MAX_CONNECTIONS = 100 // Java: Service.MAX_CONNECTIONS
@JvmOverloads
fun connect(host: String = "localhost", port: Int = 8080) {}
// Java: connect(), connect(host), connect(host, port) 오버로드 생성
}
@JvmName("setValueInt")
fun setValue(value: Int) {} // JVM 메서드명 변경 (이름 충돌 해결)
@Throws(IOException::class)
fun readFile(path: String): String { // Java checked 예외 선언
return File(path).readText()
}
}정리
리플렉션
KClass<T>: 클래스 정보,::class,primaryConstructor,memberPropertiesKFunction: 함수 참조,call(),invoke(),parametersKProperty: 프로퍼티 참조,get(),set()(mutable)typeOf<T>():reified없이 KType 획득
애노테이션
@Target,@Retention,@Repeatable— 메타 애노테이션RUNTIMEretention →findAnnotation<T>()으로 런타임 읽기@Deprecated,@Suppress,@OptIn— 표준 애노테이션@JvmStatic,@JvmField,@JvmOverloads,@Throws— Java 상호운용