Kotlin Mapのさまざまな使い方

mapOf、keys、values、mapKeys、mapValues、toSortedMap、Comparator、getOrPutなど、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型である。

Map要素の値をまとめて変更する(mapValues)

mapValuesmapKeysと同じように、Map要素の値を一括で変更する。ラムダ式は、変更後の要素の値が戻り値になるように実装する。

例: Mapのすべての値を2倍にする

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)

MutableMapgetOrPut関数を使うと、指定したキーに対応する値が見つからないとき、その値をラムダ式で初期化してから返すことができる。

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