Kotlin 고급 기능 | DSL, 리플렉션, 애노테이션

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}

정리

핵심 요약

  1. DSL: 람다 + 확장 함수로 도메인 언어 구축
  2. 리플렉션: 런타임 타입 정보 조회
  3. 애노테이션: 메타데이터 정의 및 처리
  4. inline: 람다 오버헤드 제거
  5. reified: 제네릭 타입 런타임 접근
  6. 델리게이트: 프로퍼티 동작 위임

다음 단계

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]