본문으로 건너뛰기 Kotlin 고급 기능 | DSL, 리플렉션, 애노테이션

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

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

이 글의 핵심

Kotlin 고급 기능: DSL, 리플렉션, 애노테이션. DSL (Domain Specific Language)·리플렉션 (Reflection).

들어가며

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 시리즈를 완료했습니다! 이제 다음 언어로 넘어가세요:


관련 글


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Kotlin 고급 기능: DSL, 리플렉션, 애노테이션. DSL (Domain Specific Language)·리플렉션 (Reflection)로 흐름을 잡고 원리·코드·실무 적용을 한글로 정리합니다. Start n… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. Kotlin 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

Kotlin, 고급, DSL, 리플렉션 등으로 검색하시면 이 글이 도움이 됩니다.