Kotlin Map 다양한 사용법
Map 개요
Kotlin의 Map(맵)은 Java의 Map을 기반으로 한 컬렉션이다. 키와 값의 쌍을 유지하며, 순서의 개념이 없다
Map & MutableMap
Map
은 키와 값의 쌍으로 처리한다. Map
은 읽기만 지원하는 인터페이스이다.
interface Map<K, out V>
MutableMap
은 요소의 추가 및 삭제 처리를 제공한다.
interface MutableMap<K, V> : Map<K, V>
Map 생성 (mapOf)
불변(immutable) 맵은 Kotlin에 내장된 mapOf 함수로 생성할 수 있다.
val map: Map<String, Int> = mapOf("AAA" to 1, "BBB" to 2, "CCC" to 3)
println(map["AAA"]) //=> 1
// 루프 처리(forEach 메소드도 가능)
for ((k, v) in map) {
println("$k -> $v")
}
Map 생성
불변(immutable) 맵은 Kotlin에 내장된 mapOf
함수로 생성할 수 있다.
간단한 예로 읽기만 가능한 Map
를 만들어 보도로 하겠다.
fun main() {
var map = mapOf("Red" to "#f00", "Green" to "#0f0", "Blue" to "#00f")
println(map["Red"])
// 반복 처리(forEach 함수도 가능)
for ((key, value) in map) {
println("$key = $value")
}
}
Output:
#f00
Red = #f00
Green = #0f0
Blue = #00f
Map 생성 후 요소를 추가/제거하고 싶다면 mutableMapOf
함수로 Map를 생성해야 한다.
val map: MutableMap<String, Int> = mutableMapOf("A" to 1, "B" to 2, "C" to 3)
map["A"] = 5
map["D"] = 10
for ((key, value) in map) {
println("$key -> $value")
}
Output:
A -> 5
B -> 2
C -> 3
D -> 10
이 예제에서 알 수 있듯이, 요소의 변경과 추가는 모두 map[키] = 값
이라는 표현으로 실행할 수 있다(Kotlin 내부에서 set(..)
함수 호출로 변환됨).
Map에서 키 목록, 값 목록 가져오기 (keys, values)
Map은 키-값 쌍을 갖은 콜렉션이지만, keys
속성으로 키 목록을 가져오거나, values
속성으로는 값만의 목록으로 가져올 수 있다.
val map = mapOf("A" to 1, "B" to 2, "C" to 3)
println(map.keys)
println(map.values)
Output:
[A, B, C]
[1, 2, 3]
Map의 키/값을 일괄 변경(mapKeys, mapValues)
Map 요소의 키를 일괄 변경(mapKeys)
기존 Map의 키로 사용되는 값을 일괄적으로 변경하려면 mapKeys
를 사용한다. mapKeys
에 람다식을 전달하면 각 요소의 키&값을 담고 있는 Map.Entry
객체가 해당 람다식에 순차적으로 전달된다. 각 루프 처리에서 람다식이 반환하는 반환값이 새로운 키로 처리된다. Map.Entry
객체에서는 key
로 요소의 키를, value
로 요소의 값을 참조할 수 있다.
예: Map의 모든 키를 대문자로 변환
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
val updatedMap = map.mapKeys { it.key.uppercase() }
println(updatedMap)
Output:
{A=1, B=2, C=3}
mapKeys
의 반환값은 Map 타입이다.
맵 요소의 값을 함께 변경(mapValues)
mapValues모두 mapKeys비슷하지만 맵 요소의 값을 함께 변경합니다. 람다 식은 변경 후 요소의 값이 반환 값이 되도록 구현합니다.
mapValues
도 mapKeys
와 비슷하게 mapValues
은 Map 요소의 값을 일괄적으로 변경한다. 람다식은 변경된 요소의 값이 반환값이 되도록 구현한다.
예: Map의 모든 값을 두 배로 만들기
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
val updatedMap = map.mapValues { it.value * 2 }
println(updatedMap)
Output:
{a=2, b=4, c=6}
mapValues
의 반환값은 Map 타입이다.
Map 정렬 및 반복 처리(toSortedMap)
Map을 키별로 정렬
Map의 요소를 출력할 때에 키순으로 정렬하여 출력해야 하는 경우가 있을 것이다. 이런 경우 toSortedMap
을 사용하면 키로 정렬된 맵(SortedMap
)으로 변환할 수 있다.
val map = mapOf("C" to 3, "A" to 1, "B" to 2)
for ((k, v) in map.toSortedMap()) {
println("$k -> $v")
}
Output:
A -> 1
B -> 2
C -> 3
forEach
함수를 사용하여 루프를 작성하면, 처리 흐름이 왼쪽에서 오른쪽으로 일방향으로 진행되기 때문에 가독성이 다소 높아질 수 있다.
map.toSortedMap().forEach { k, v ->
println("$k -> $v")
}
sortedMapOf
라는 팩토리 함수를 사용하여 처음부터 SortedMap
타입으로 맵 인스턴스를 생성하는 방법도 있다.
val map = sortedMapOf("C" to 3, "A" to 1, "B" to 2)
map.forEach { k, v ->
println("$k -> $v")
}
Map을 값으로 정렬하기
Map 요소의 값을 기준으로 정렬하고 해야 하는 경우도 있을 것이다. 몇 가지 방법을 알아보겠다.
키 & 값 목록으로 변환하여 값으로 정렬하는 방법
val map = mapOf("A" to 3, "B" to 1, "C" to 2)
val pairs = map.toList().sortedBy { it.second }
pairs.forEach { (k, v) -> println("$k, $v") }
Output:
B, 1
C, 2
A, 3
이 방법은 Map을 List<Pair<String, Int>>
로 변환하여 값으로 정렬하여 출력하였다.
toSortedMap()
에 사용자 지정 Comparator를 전달하는 방법
val map = mapOf("A" to 3, "B" to 1, "C" to 2)
val map2 = map.toSortedMap { k1, k2 ->
map[k1]!! - map[k2]!!
}
map2.forEach { (k, v) -> println("$k, $v") }
Output:
B, 1
C, 2
A, 3
이 방법은 toSortedMap
에 Comparator를 람다식으로 전달하여 정렬하고 출력하였다.
Map의 값을 처음 가져오려고 할 때에, 초기화(Map의 지연 초기화)(getOrPut)
MutableMap
의 getOrPut 함수를 사용하면 지정된 키에 해당하는 값을 찾을 수 없을 때, 해당 값을 람다식으로 초기화한 후 반환할 수 있다.
val map = mutableMapOf<String, Int>()
println(map["foo"]) // null (값이 존재하지 않음)
println(map.getOrPut("foo") { 0 }) // 0 (get과 동시에 초기값이 설정됨)
println(map["foo"]) // 0 (값이 설정되어 있음)
Output:
null
0
0
getOrPut()
함수를 사용하면 맵 값의 지연 초기화를 할 수 있다. 키&값 형식의 고정된 값을 얻고 싶지만, 각 값을 얻기 위해서는 다소 비용이 많이 드는 경우 캐시 용도로 사용할 수 있다(값이 변하지 않는다는 전제 하에).
class UserDb {
// 사용자 나이 캐시
private var userAge = mutableMapOf<String, Int>()
// 사용자 나이를 가져온다(캐시를 이용).
fun getAge(name: String): Int = userAge.getOrPut(name) { getAgeWithoutCache(name) }
private fun getAgeWithoutCache(name: String): Int {
// 초기값 계산에 시간이 걸린다고 가정
return 10
}
}
fun main() {
val userDb = UserDb()
val age: Int = userDb.getAge("devkuma")
println(age)
}
Output:
10