33
18

More than 5 years have passed since last update.

KotlinとMap

Last updated at Posted at 2019-01-01

はじめに

はるか昔に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についてみていきます。

val map = mutableMapOf(1 to "hogehoge")

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

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

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

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

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

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

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

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

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

Mapを操作する

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

map[2] = "hogehoge2"

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

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

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

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

map.putIfAbsent(2, "change hogehoge2")

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

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

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

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

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

Mapを判定する

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

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

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

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

map.orEmpty()

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

Mapから値を取り出す

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

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

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

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

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

map.getOrDefault(15, "default")

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

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

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

map.keys
map.values
map.entries
map.size

readOnlyMap

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

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

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

val map = mapOf(1 to "hogehoge")

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

おわりに

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

    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)]}
    }
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は機能を知れば知るほど色々表現が出来るようになるので勉強が楽しい点もいいですね。

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

33
18
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
18