(Kotlin/코틀린) Map delegation — Map에서 프로퍼티 읽기


1. Map delegation이란?

Kotlin은 MapMutableMapgetValue/setValue가 내장되어 있어
Map을 프로퍼티 델리게이트로 사용할 수 있습니다.

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
    val email: String by map
}

val user = User(mapOf(
    "name" to "Alice",
    "age" to 30,
    "email" to "alice@example.com"
))

println(user.name)   // Alice
println(user.age)    // 30
println(user.email)  // alice@example.com

프로퍼티 이름이 Map의 키로 사용됩니다.


2. var 프로퍼티 — MutableMap 위임

class MutableUser(val map: MutableMap<String, Any?>) {
    var name: String by map
    var age: Int by map
}

val user = MutableUser(mutableMapOf(
    "name" to "Bob",
    "age" to 25
))

user.name = "Charlie"  // map["name"] = "Charlie"
user.age = 26          // map["age"] = 26

println(user.map)  // {name=Charlie, age=26}

3. 키 없을 때 동작

Map에 해당 키가 없으면 NoSuchElementException이 발생합니다.

class Config(val map: Map<String, Any?>) {
    val host: String by map
    val port: Int by map
}

val config = Config(mapOf("host" to "localhost"))
println(config.host)  // localhost
println(config.port)  // NoSuchElementException: Key port is missing in the map.

기본값이 필요하면 withDefault를 사용합니다.

class Config(map: Map<String, Any?>) {
    private val safeMap = map.withDefault { key ->
        when (key) {
            "host" -> "localhost"
            "port" -> 8080
            else   -> null
        }
    }

    val host: String by safeMap
    val port: Int by safeMap
}

4. 실전 패턴 — JSON 응답 파싱

// API 응답이 Map으로 들어오는 경우 (Gson, org.json 등)
class ApiResponse(private val json: Map<String, Any?>) {
    val userId: Int by json
    val userName: String by json
    val email: String by json
    val createdAt: String by json
    val isActive: Boolean by json
}

// 사용
val jsonMap = mapOf(
    "userId" to 1,
    "userName" to "Alice",
    "email" to "alice@example.com",
    "createdAt" to "2026-01-01",
    "isActive" to true
)

val response = ApiResponse(jsonMap)
println("${response.userName} (${response.email})")  // Alice (alice@example.com)

5. 실전 패턴 — 동적 설정 클래스

class AppConfig(private val properties: Map<String, String>) {
    val databaseUrl: String by properties
    val databaseUser: String by properties
    val databasePassword: String by properties
    val maxPoolSize: String by properties
    val debugMode: String by properties
}

// .properties 파일이나 환경변수로부터 로드
fun loadConfig(): AppConfig {
    val props = mapOf(
        "databaseUrl" to System.getenv("DB_URL"),
        "databaseUser" to System.getenv("DB_USER"),
        "databasePassword" to System.getenv("DB_PASSWORD"),
        "maxPoolSize" to (System.getenv("POOL_SIZE") ?: "10"),
        "debugMode" to (System.getenv("DEBUG") ?: "false")
    )
    return AppConfig(props.filterValues { it != null }.mapValues { it.value!! })
}

6. 실전 패턴 — 플러그인 시스템

// 런타임에 동적으로 설정되는 속성들
class PluginConfig(private val settings: MutableMap<String, Any?> = mutableMapOf()) {
    var name: String by settings
    var version: String by settings
    var enabled: Boolean by settings
    var priority: Int by settings

    fun toMap(): Map<String, Any?> = settings.toMap()
}

val plugin = PluginConfig()
plugin.name = "DataExporter"
plugin.version = "1.0.0"
plugin.enabled = true
plugin.priority = 10

println(plugin.toMap())
// {name=DataExporter, version=1.0.0, enabled=true, priority=10}

7. Map delegation vs data class

상황 Map delegation data class
키가 런타임에 결정됨 적합 부적합
추가 필드가 자주 생김 적합 매번 수정 필요
타입 안전성 약함 (캐스팅 필요) 강함
IDE 자동완성 프로퍼티 이름으로 지원 완전 지원
JSON 파싱 편리 data class 선호
직렬화 별도 처리 필요 @Serializable 간단

고정된 구조라면 data class, 동적 구조나 외부 설정 로딩에는 Map delegation이 유용합니다.


8. 타입 안전성 개선

// 제네릭 헬퍼로 타입 캐스팅 숨기기
class TypedMap(private val map: Map<String, Any?>) {
    inline operator fun <reified T> getValue(thisRef: Any?, property: KProperty<*>): T {
        val value = map[property.name]
            ?: throw NoSuchElementException("키 없음: ${property.name}")
        return value as? T
            ?: throw ClassCastException("${property.name}: ${value::class.simpleName} → ${T::class.simpleName} 변환 실패")
    }
}

class StronglyTypedConfig(map: Map<String, Any?>) {
    private val typedMap = TypedMap(map)
    val host: String by typedMap
    val port: Int by typedMap
    val debug: Boolean by typedMap
}

9. 정리



Related Posts