この記事で分かること
- map と flatMap の動作の違いについて
- map と flatMap の使い分け方について
- map と flatMap の違いを理解できるサンプルコードあり
この記事では、Kotlinの map / flatMap の違いについて解説しています。
どちらも値を変換する機能なので、その違いを理解するのが難しいですよね?
このページでは結論を述べた後、サンプルコードを使って両者の違いやユースケースを理解できるようにしています。
KotlinとJavaを比較した例もあるので、参考にしてみて下さい。
map vs flatMap は定番のテーマ!ぜひ理解して帰ってください!
結論から
Kotlinの flatMap は、map と flatten が一緒になったものと理解できます。
map + flatten = flatMap
続いて、それぞれをどのようなケースで使用するかについて説明します。
ポイント
- map は、1つの入力が1つの出力になる場合に使用する
- flatMap は、1つの入力が複数の出力に増える場合に使用する
flatMap を使う場合は例えば、
- APIに検索条件を渡すと結果が複数返ってくるケース
- DBを検索すると結果が複数返ってくるケース
などのユースケースが考えられます。
ここまでが結論です。続いてサンプルコードで内容を解説します。
サンプルコード
まずは簡単なサンプルコードを使って、それぞれの動作を理解しましょう。
map と flatMap の動作の違いを説明した後、Java の stream との比較を行います。
基本的な map / flatMap の動作を理解する
fun example() {
val list = listOf(1, 2, 3)
// map
val convertedByMap = list
.map { listOf(it, it) }
println(convertedByMap)
// [[1, 1], [2, 2], [3, 3]]
// map + flatten
val convertedByMapFlatten = list
.map { listOf(it, it) }
.flatten()
println(convertedByMapFlatten)
// [1, 1, 2, 2, 3, 3]
// flatMap
val convertedByFlatMap = list
.flatMap { listOf(it, it) }
println(convertedByFlatMap)
// [1, 1, 2, 2, 3, 3]
}
Javaの map / flatMap との比較
Javaで上記のKotlinとほぼ同じコードを書いてみます。
Javaにはflattenが無いため、map と flatMap のコード例のみになります。
public void example() {
List<Integer> list = Arrays.asList(1, 2, 3);
// map
List<List<Integer>> convertedByMap = list
.stream()
.map(it -> Arrays.asList(it, it))
.collect(Collectors.toList());
System.out.println(convertedByMap);
// [[1, 1], [2, 2], [3, 3]]
// flatMap
List<Integer> convertedByFlatMap = list
.stream()
.flatMap(it -> Stream.of(it, it))
.collect(Collectors.toList());
System.out.println(convertedByFlatMap);
// [1, 1, 2, 2, 3, 3]
}
書き方は異なりますが、Kotlinのmap/flatMapと同じ役割を持つと考えていいと思います。
ユースケースの考察
mapは1対1で変換するものなので、比較的用途が想像しやすいと思います。
多くの方は flatMapの用途やユースケースが分かりにくいと思うので、一つサンプルコードを示します。
以下のサンプルコードでは、APIにクエリー文字列を渡すと、レスポンスとしてそのクエリー文字列が含まれるツイートのIDを返すTwitterのAPIがあると仮定してください。
fun example2() {
val queryList = listOf("Java", "Kotlin", "駆け出しエンジニア")
val convertedByFlatMap = queryList
.flatMap { callTwitterApi(it) } // Twitter APIを呼んでるつもり
println(convertedByFlatMap)
// [1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
private fun callTwitterApi(query: String): List<Long> {
// Query文字列で検索すると、Queryを含むツイートのIDを返すAPIと仮定する
return LongRange(1, query.length.toLong()).toList() // APIのレスポンスのつもり
}
1つのクエリー文字列に対して、複数のIDがレスポンスされますが、それらのIDをまとめて一つのListに変換するケースを想定しています。
このケースの様に、flatMapは 1個 -> N個の出力になる場合などに使うと威力を発揮すると思います。
flatMapを使わずにコードを書こうとすると、以下の様になります。
fun example3() {
val queryList = listOf("Java", "Kotlin", "駆け出しエンジニア")
val resultList = mutableListOf<Long>()
for (query in queryList) {
val ids = callTwitterApi(query)
resultList.addAll(ids)
}
println(resultList)
// [1, 2, 3, 4, 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
flatMapを使った例の方がかなりシンプルに書けることが分かります。
まとめ
最後に、この記事の内容をまとめます。
要点まとめ
- flatMap = map + flatten
- map は 1 -> 1 の変換に使用する
- flatMapは 1 -> N の変換に使用する
map と flatMap は機能が似ているので、どのように使い分けたらよいか少し難しいです。
今回示したようなユースケースを念頭に置いて map / flatMap を使い分ければ、自信をもってどちらを使うかを選ぶことができると思います。
実際私も使い分けが不安だったのですが、今回の記事を書くことで理解が深まりました
もし、参考になりましたら幸いです。それでは、他の記事でお会いしましょう!