KotlinとMap

# はじめに
はるか昔にListについての記事を書きましたが、色々あってKotlinの勉強を中々出来ませんでしたが、最近やっと復帰できたのでMapについて今回勉強しました。

# 環境
Java11 Oracle OpenJDK
IntelliJ
Kotlin 1.31

# mapの種類
Listと同じくmutableなMapとreadOnlyなmapの2種類を持っています。

Listもそうですが、immutableなMapについてはstandardな実装とはならずembeddable側に実装されていて自力生成するというよりはmutableなmapをコピーしてimmutableするような使い方が想定されているようです。
(新規作成するofメソッドは空Mapか1つ分のKey-Valueしか渡せません)

また、これから出てくるMapの生成メソッドについて、Element1つの場合はimmutable、可変長引数を使用する場合はreadOnlyになると公式に書いてありましたので、そういう感じです。

# mutableMap
まずはmutableなMapについてみていきます。

``` Kotlin
val map = mutableMapOf(1 to "hogehoge")
```

KotlinのMutableMap生成メソッドである`mutableMapOf`については`Pair<K,V>`を引数とします(複数指定可)。
そのため、少し書き方が特殊です。

`to`については変数を使用して指定することも可能となっています。

``` Kotlin
val id = 1
val name = "hogehoge"
val map = mutableMapOf(id to name)
```

なので、このような指定も可能となっています。

生成後の型については1.31現在では`kotlin.collections.LinkedHashMap`に変換しているようです。

`LinkedHashMap`以外にも`HashMap`にしたい場合のメソッドも存在しています。

``` Kotlin
val id = 1
val name = "hogehoge"
val map = hashMapOf(id to name)
```

そもそも`LinkedHashMap`を指定するメソッドも存在していたりします。

``` Kotlin
val id = 1
val name = "hogehoge"
val map = linkedMapOf(id to name)
```

## Mapを操作する
Mapを追加する際は`put`が用意されているためこちらを使うのだろうと思っていたらIntelliJでは警告を発していました。
どうやら`put`は使用せずにindexing operatorで追加するほうが良いようです。

``` Kotlin
map[2] = "hogehoge2"
```

もちろん`put`が使用出来ない訳ではありませんが、警告を無視する訳にはいかないので、こちらを覚えておきましょう。

ただし、`put`もindexing operatorの指定もキーが重複した場合には上書きするという動作で統一されています。

`put`はJavaと同様に戻り値がありますが、必要ない場合に使用する`set`も存在していますが、こちらも同様の理由で警告が発します。

上書きしたくない場合は`putIfabsent`を使用することで元の値を保持してくれます。

``` Kotlin
map.putIfAbsent(2, "change hogehoge2")
```

生成時にPairを扱っているのもあり、Pairからの追加も可能になっています。
ただし、副作用無しで新しいMapを生成するため、きちんと値を受け取ってあげる必要があります。

下の例ではPair1つ受け取っていますが、複数受け取ることも可能です。

``` Kotlin
val pair = Pair(2, "hogehoge")
map.plus(pair)
```

削除する場合は`remove`で統一されていますが、keyとvalue両方とも指定したりすることも出来るので、より柔軟に削除することが可能になっています。

``` Kotlin
map.remove(2)
map.remove(2, "hogehoge2")
```

## Mapを判定する
Map内にkeyが存在することの確認として`containsKey`の実装されているのと共にvalueの存在確認として`containsValue`も実装されているためどちらの存在確認も可能となっています。

Mapの存在チェックやNullチェックについても一揃いしています。
`isNullOrEmpty`以外はNullの場合はビルドエラーとなってしまいますので、Nullの状況によって使い分けてあげてください。

``` Kotlin
map?.isEmpty())
map?.isNotEmpty())
map.isNullOrEmpty()
```

`Map<K,V>?` -> `Map<K,V>`への変換用のメソッドも存在しています。Kotlin内部だけなら必要ありませんが、Javaと両立している環境ではあった方が楽です。

``` Kotlin
map.orEmpty()
```

他にも特定の条件が存在するか確認したりなんだりとMapの中身を判定するものが存在しているため、やはり柔軟な使用が出来るようになっています。

## Mapから値を取り出す
KotlinのMapから値を取り出す方法は様々です。
通常の`get`も存在しますが、`put`と同様にindexing operatorの使用が推奨されています。

``` Kotlin
NG : map.get(1)
OK : map[1]
```

存在しないkeyからの取り出しをすると`null`が返却される仕様となっていますが、`getOrDefault`や`getOrElse`でkeyが存在しない場合の振る舞いを決めることが出来ます。

違いは値を返却するか処理(Lambda)を返すかの違いです。

`getOrElse`については元のvalueから型を変更しても問題ないため、意味の分からない処理もかけてしまうため注意が必要です。

``` Kotlin
map.getOrDefault(15, "default")

map.getOrElse(15) {
    "hogehoge".length
}
```

普通に取得する以外にも抽出して値を取得したり変換したりも可能となっていますが、量が多すぎるので割愛します。

さらに色んなものをまとめて取得することも可能となっています。

``` Kotlin
map.keys
map.values
map.entries
map.size
```

# readOnlyMap
readOnlyのMapを生成するには`mapOf`を使用します。
このメソッドは`mutableMapOf`と同じ`Maps.kt`と`MapsJVM.kt`の両方に存在していますが、`MapsJVM.kt`の方が使用されます。
Mapを複数個にした場合は前者の方が使用されるようですが、classを確認すると1つでも複数でも同じクラスとして取得されるので、ここの仕組みは良く分からないです。

生成している型はKotlin1.31時点で`java.util.Collections.singletonMap`となっています。

たぶん拡張からだと思うのですが、きちんと複数のkeyを扱うことが出来ます。

``` Kotlin
val map = mapOf(1 to "hogehoge")
```

readOnlyという形になっていますが、singletonMapを使用しているため、実質的にImmutableのようです。
そもそもMapについては副作用を与える操作は少ないので、あまり気にはならないかもしれません。

# おわりに
JavaのMapもStreamの登場によって便利になった部分も、既にKotlinで実装可能です。
前にgoupingByの記事で書いたような集約をやらせてみましたが、同じように動作することが分かっています。

``` Java
    public static void main(String[] arg) {
        List<String> names = List.of("taro", "jiro", "saburo");

        List<Name> nameList = names.stream().map(it -> new Name(it, it, it)).collect(Collectors.toList());
        nameList.add(new Name("taro", "jiro", "taro"));

        Map<String,List<Name>> map = nameList.stream()
                .collect(Collectors.groupingBy(it -> it.getName1() + "-" + it.getName3()));
        System.out.println(map);
        // => {jiro-jiro=[Main.Name(name1=jiro, name2=jiro, name3=jiro)], saburo-saburo=[Main.Name(name1=saburo, name2=saburo, name3=saburo)], taro-taro=[Main.Name(name1=taro, name2=taro, name3=taro), Main.Name(name1=taro, name2=jiro, name3=taro)]}
    }
```

``` Kotlin
fun main(args : Array<String>) {
    val names = listOf("taro", "jiro", "saburo")
    val nameList = names.map { Name(it, it, it) }.plus(Name("taro", "jiro", "taro"))
    val map = nameList.groupBy { s -> s.name1 + "-" + s.name3 }
    println(map)
    // -> {taro-taro=[Name(name1=taro, name2=taro, name3=taro), Name(name1=taro, name2=jiro, name3=taro)], jiro-jiro=[Name(name1=jiro, name2=jiro, name3=jiro)], saburo-saburo=[Name(name1=saburo, name2=saburo, name3=saburo)]}

}

data class Name(val name1: String, val name2: String, val name3: String)
```

やっぱりKotlinって楽だなーとやっていて思いました。
表現力が高いKotlinは機能を知れば知るほど色々表現が出来るようになるので勉強が楽しい点もいいですね。

これからも色々と勉強していきたいです。