Kotlin 고급 기능 | DSL, 리플렉션, 애노테이션
이 글의 핵심
Kotlin 고급 기능에 대한 실전 가이드입니다. DSL, 리플렉션, 애노테이션 등을 예제와 함께 상세히 설명합니다.
들어가며
DSL 빌더·인라인·리플렉션 등은 라이브러리 API를 부드럽게 만드는 데 쓰입니다. 팀이 읽기 쉬운 수준으로만 도입하는 것이 좋습니다.
1. DSL (Domain Specific Language)
DSL이란?
DSL은 특정 도메인에 특화된 언어입니다. Kotlin의 람다와 확장 함수를 활용해 자연스러운 DSL을 만들 수 있습니다.
HTML DSL 예제
class HTML {
private val content = StringBuilder()
fun body(block: Body.() -> Unit) {
content.append("<body>")
Body().apply(block).render(content)
content.append("</body>")
}
fun render() = content.toString()
}
class Body {
private val content = StringBuilder()
fun h1(text: String) {
content.append("<h1>$text</h1>")
}
fun p(text: String) {
content.append("<p>$text</p>")
}
fun render(sb: StringBuilder) {
sb.append(content)
}
}
fun html(block: HTML.() -> Unit): HTML {
return HTML().apply(block)
}
// 사용
val page = html {
body {
h1("제목")
p("내용입니다.")
}
}
println(page.render())
테스트 DSL
class TestSuite {
private val tests = mutableListOf<Test>()
fun test(name: String, block: () -> Unit) {
tests.add(Test(name, block))
}
fun run() {
tests.forEach { it.run() }
}
}
data class Test(val name: String, val block: () -> Unit) {
fun run() {
try {
block()
println("✓ $name")
} catch (e: AssertionError) {
println("✗ $name: ${e.message}")
}
}
}
fun suite(block: TestSuite.() -> Unit): TestSuite {
return TestSuite().apply(block)
}
// 사용
suite {
test("덧셈 테스트") {
assert(1 + 1 == 2)
}
test("곱셈 테스트") {
assert(2 * 3 == 6)
}
}.run()
2. 리플렉션 (Reflection)
기본 리플렉션
import kotlin.reflect.full.*
class User(val name: String, val age: Int) {
fun greet() = "안녕하세요, $name님!"
}
fun main() {
val user = User("홍길동", 25)
val kClass = user::class
// 클래스 정보
println("클래스명: ${kClass.simpleName}")
println("패키지: ${kClass.qualifiedName}")
// 프로퍼티 조회
kClass.memberProperties.forEach { prop ->
println("${prop.name}: ${prop.get(user)}")
}
// 함수 조회
kClass.memberFunctions.forEach { func ->
println("함수: ${func.name}")
}
}
리플렉션으로 객체 생성
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.primaryConstructor
class Person(val name: String, val age: Int)
fun main() {
val kClass = Person::class
// 생성자로 생성
val constructor = kClass.primaryConstructor!!
val person = constructor.call("홍길동", 25)
println("${person.name}, ${person.age}")
}
3. 애노테이션 (Annotation)
커스텀 애노테이션
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Entity(val tableName: String)
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class Column(val name: String)
@Entity(tableName = "users")
class User(
@Column(name = "user_name")
val name: String,
@Column(name = "user_age")
val age: Int
)
애노테이션 처리
import kotlin.reflect.full.*
fun getTableName(obj: Any): String? {
val annotation = obj::class.findAnnotation<Entity>()
return annotation?.tableName
}
fun main() {
val user = User("홍길동", 25)
println("테이블명: ${getTableName(user)}") // users
}
4. 인라인 함수 (Inline Functions)
기본 인라인
inline fun measureTime(block: () -> Unit) {
val start = System.currentTimeMillis()
block()
val end = System.currentTimeMillis()
println("실행 시간: ${end - start}ms")
}
measureTime {
Thread.sleep(100)
}
reified 타입 매개변수
inline fun <reified T> isInstance(value: Any): Boolean {
return value is T
}
inline fun <reified T> List<*>.filterIsInstance(): List<T> {
return this.filter { it is T }.map { it as T }
}
fun main() {
println(isInstance<String>("Hello")) // true
println(isInstance<Int>("Hello")) // false
val mixed: List<Any> = listOf(1, "two", 3, "four", 5)
val strings = mixed.filterIsInstance<String>()
println(strings) // [two, four]
}
noinline과 crossinline
inline fun foo(
inlined: () -> Unit,
noinline notInlined: () -> Unit
) {
inlined()
notInlined()
}
inline fun bar(crossinline block: () -> Unit) {
val runnable = Runnable { block() }
runnable.run()
}
5. 델리게이트 (Delegates)
프로퍼티 델리게이트
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("초기값") { prop, old, new ->
println("${prop.name}: $old → $new")
}
var age: Int by Delegates.vetoable(0) { prop, old, new ->
new >= 0 // 음수 거부
}
}
fun main() {
val user = User()
user.name = "홍길동" // name: 초기값 → 홍길동
user.age = 25
user.age = -1 // 거부됨
println(user.age) // 25
}
lazy 델리게이트
val heavyObject: String by lazy {
println("초기화 중...")
"무거운 객체"
}
fun main() {
println("시작")
println(heavyObject) // 여기서 초기화
println(heavyObject) // 재사용
}
6. 실전 예제
예제: JSON DSL
class JsonObject {
private val properties = mutableMapOf<String, Any>()
infix fun String.to(value: Any) {
properties[this] = value
}
fun toJson(): String {
return properties.entries.joinToString(
separator = ", ",
prefix = "{",
postfix = "}"
) { (k, v) ->
val valueStr = if (v is String) "\"$v\"" else v.toString()
"\"$k\": $valueStr"
}
}
}
fun json(block: JsonObject.() -> Unit): JsonObject {
return JsonObject().apply(block)
}
// 사용
val user = json {
"name" to "홍길동"
"age" to 25
"active" to true
}
println(user.toJson())
// {"name": "홍길동", "age": 25, "active": true}
정리
핵심 요약
- DSL: 람다 + 확장 함수로 도메인 언어 구축
- 리플렉션: 런타임 타입 정보 조회
- 애노테이션: 메타데이터 정의 및 처리
- inline: 람다 오버헤드 제거
- reified: 제네릭 타입 런타임 접근
- 델리게이트: 프로퍼티 동작 위임
다음 단계
Kotlin 시리즈를 완료했습니다! 이제 다음 언어로 넘어가세요:
- TypeScript 시작하기
- Rust 시작하기
- Java 시작하기
관련 글
- C++26 리플렉션 기초 | ^^ 연산자·std::meta::info로 타입 정보 조회하기
- C++ 컴파일 타임 리플렉션 | C++26 Reflection·magic_enum·매크로 직렬화·검증
- C++26 핵심 기능 완벽 가이드 | 리플렉션 ^^· std::execution
- C++ 리플렉션 구현 | 타입 정보·메타데이터·자동 직렬화 [#55-1]
- C++ 직렬화 완벽 가이드 | JSON·바이너리·Protobuf·리플렉션 기반 자동화 [#55-5]