KotlinのflatMapの完全解説!たった10のサンプルコードでプロ級に

Kotlin言語のflatMap関数のイラストとサンプルコードのスクリーンショットKotlin
この記事は約16分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

この記事を読めば、KotlinのflatMap関数を使ったコーディングが簡単にできるようになります。

Kotlinとは、Javaよりもシンプルで、Androidアプリの開発にも使われる人気のあるプログラミング言語です。

その中で、flatMap関数はリストや配列を効率的に変換するための強力なツールとして知られています。

しかし、flatMapの真の力を引き出すには、その動作の仕組みや使い方を正確に理解する必要があります。

この記事では、初心者から中級者までの読者がKotlinのflatMapをしっかりと理解できるように、具体的なサンプルコードと共にその使用方法を徹底的に解説します。

Kotlinのプログラミングにおいて、flatMapの知識は避けて通れないものと言っても過言ではありません。

より効率的なコーディング、そしてコードの可読性を高めるためにも、この関数の使い方をマスターすることをおすすめします。

●flatMapとは?

KotlinにおけるflatMapは、コレクションや配列に含まれる要素を変換し、それを新しいコレクションや配列に平坦化して返す関数です。

つまり、入れ子になったリストや配列を、1つのリストや配列に変換する際に非常に役立ちます。

具体的には、コレクションや配列の各要素に関数を適用して新しいコレクションや配列を生成し、それらを一つに結合します。

この結果、元のコレクションや配列の構造が平坦化されます。

また、flatMapはKotlinだけでなく、他のプログラミング言語やライブラリでも同様の機能を持つ関数として提供されていることが多いです。

そのため、KotlinのflatMapを理解することは、他のプログラミング言語やフレームワークとの連携においても大きなアドバンテージとなります。

○KotlinでのflatMapの基本

KotlinでのflatMapの基本的な使い方は、コレクションや配列に対して、flatMap関数を呼び出し、変換する関数を引数として渡すというものです。

この変換する関数の中で、新しいコレクションや配列を生成し、それが結果として返されると、その全ての要素が1つのコレクションや配列に平坦化されて返されます。

例えば、ある人が所有している複数の本のリストがあり、それぞれの本には複数の著者が関連付けられているとします。

この場合、所有しているすべての本の著者のリストを取得するには、flatMapを使って各本の著者のリストを1つに結合することができます。

●flatMapの使い方

KotlinでのflatMapの基本的な使い方を知る前に、まずはmap関数に触れることで、二つの違いを理解しやすくします。

mapはコレクションや配列の各要素に対して関数を適用し、その結果を新しいコレクションや配列として返します。

一方で、flatMapmapと同じように関数を適用するものの、その結果を平坦化したコレクションや配列として返します。

○サンプルコード1:基本的なflatMapの使用法

このコードではリスト内の各要素を別のリストとして返す関数を適用しています。

fun main() {
    val list = listOf(1, 2, 3)
    val result = list.flatMap { value -> listOf(value, value * 10) }
    println(result)
}

このコードでは、listOf(1, 2, 3)の各要素に対して、それ自体とその10倍の値を持つ新しいリストを生成しています。

そして、これらの小さなリストを1つの大きなリストに結合します。

このコードを実行すると、次のような結果が得られます。

[1, 10, 2, 20, 3, 30]

○サンプルコード2:リスト内の要素変換

このコードでは、名前のリストからそれぞれの名前の文字数を持つリストを生成しています。

fun main() {
    val names = listOf("Alice", "Bob", "Charlie")
    val nameLengths = names.flatMap { name -> listOf(name.length) }
    println(nameLengths)
}

このコードでは、listOf("Alice", "Bob", "Charlie")の各要素の文字数を取得して新しいリストを生成します。

このコードを実行すると、次の結果が得られます。

[5, 3, 7]

これで、元のリストの各要素の文字数を簡単に取得できました。

○サンプルコード3:nullを排除しながらの変換

Kotlinではnull安全な言語として知られています。

このため、nullを持つ可能性のある要素を持つリストを扱う際には特別な注意が必要です。

flatMap関数を使用すると、nullの要素を簡単に取り除くことができます。

このコードでは、文字列のリストの中でnullを含む可能性のある要素を取り除き、それ以外の要素を大文字に変換して新しいリストを生成します。

fun main() {
    val words: List<String?> = listOf("apple", null, "banana", "cherry", null)
    val processedWords = words.flatMap { it?.toUpperCase()?.let { word -> listOf(word) } ?: listOf() }
    println(processedWords)
}

このコードではlistOf("apple", null, "banana", "cherry", null)というリストに対して、flatMapを用いてnullの要素を排除しつつ、nullでない要素は大文字に変換しています。

上記のコードを実行すると、次のような結果が出力されます。

[APPLE, BANANA, CHERRY]

このように、flatMapを使用することでnullを持つ要素を簡単にフィルタリングし、その他の要素に対して所望の処理を行うことができます。

○サンプルコード4:複数のリストを結合

リストの中にリストが含まれている場合、flatMapを使用することでこれらを1つのリストに結合することができます。

これはリストのリストを平坦化する場面で非常に有用です。

このコードでは、数値のリストのリストを1つのリストに平坦化します。

fun main() {
    val lists: List<List<Int>> = listOf(listOf(1, 2, 3), listOf(4, 5, 6), listOf(7, 8, 9))
    val flatList = lists.flatMap { it }
    println(flatList)
}

このコードではlistOf(listOf(1, 2, 3), listOf(4, 5, 6), listOf(7, 8, 9))というリストのリストに対して、flatMapを用いてそれを平坦化しています。

上記のコードを実行すると、次のような結果が得られます。

[1, 2, 3, 4, 5, 6, 7, 8, 9]

このように、flatMapはリストの中のリストを1つのリストに結合するときにも非常に役立ちます。

この処理は、多次元のデータを1次元に変換する際など、さまざまな場面で利用することができます。

●flatMapの応用例

flatMap関数はKotlinにおける強力なツールの1つです。

ここでは、flatMapの応用的な使い方をいくつかのサンプルコードとともに探っていきます。

○サンプルコード5:条件を組み合わせたデータの変換

たとえば、特定の条件に合致するデータのみを変換したい場合、flatMapと他の関数を組み合わせることで簡単に実現できます。

このコードでは、偶数のみを2倍にして新しいリストを生成します。

fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)
    val processedNumbers = numbers.flatMap { if (it % 2 == 0) listOf(it * 2) else listOf() }
    println(processedNumbers)
}

上記のコードを実行すると、[4, 8]というリストが出力されます。

この方法で、特定の条件を満たすデータだけを変換し、新しいリストを作成することができます。

○サンプルコード6:flatMapを用いたデータフィルタリング

flatMapはデータの変換だけでなく、フィルタリングの用途にも使用できます。

特定の条件を満たすデータのみを取得することが可能です。

このコードでは、文字数が5文字以上の文字列だけを新しいリストとして取得します。

fun main() {
    val words = listOf("apple", "banana", "cherry", "date", "fig")
    val longWords = words.flatMap { if (it.length >= 5) listOf(it) else listOf() }
    println(longWords)
}

このコードを実行すると、[apple, banana, cherry]という結果が得られます。

○サンプルコード7:flatMapと他の関数との組み合わせ

flatMap関数は他のKotlinの関数と組み合わせて使用することで、さらに柔軟かつ強力な操作が可能となります。

ここでは、flatMapmapfilterなどの関数と組み合わせた使用例を紹介します。

このコードでは、リスト内の文字列を大文字化した後、文字数が5文字以上のもののみを取得します。

fun main() {
    val words = listOf("apple", "banana", "cherry", "date", "fig")
    val processedWords = words
        .map { it.toUpperCase() }
        .flatMap { if (it.length >= 5) listOf(it) else listOf() }
    println(processedWords)
}

このコードではmap関数を使ってすべての文字列を大文字に変換した後、flatMapを使用して文字数が5文字以上のもののみを取得しています。

実行結果は、[APPLE, BANANA, CHERRY]となります。

○サンプルコード8:複雑なデータ構造の変換

flatMapは複雑なデータ構造の操作にも適しています。

例えば、リストのリストや、ネストされたオブジェクトのコレクションなど、入れ子になったデータ構造を扱う場合にも効果を発揮します。

このコードでは、リストのリストを平坦化し、全ての要素を合計します。

fun main() {
    val listOfLists = listOf(
        listOf(1, 2, 3),
        listOf(4, 5, 6),
        listOf(7, 8, 9)
    )
    val flatList = listOfLists.flatMap { it }
    val sum = flatList.sum()
    println("合計値: $sum")
}

このコードでは、まずflatMapを使用してネストされたリストを平坦化します。

その後、得られたリストの全ての要素の合計を求めます。

結果として、合計値は45となります。

○サンプルコード9:非同期処理との連携

Kotlinの非同期処理ライブラリであるcoroutinesflatMap関数は、連携して非常に効率的な非同期処理を実現することができます。

特に、非同期処理を行った結果を変換やフィルタリングしたい場合、flatMapが大変役立ちます。

下記のサンプルコードは、非同期に取得した複数のリストをflatMapを用いて平坦化する例を表しています。

import kotlinx.coroutines.*

fun main() = runBlocking {
    val data1 = async { listOf(1, 2, 3) }
    val data2 = async { listOf(4, 5, 6) }
    val data3 = async { listOf(7, 8, 9) }

    val allData = listOf(data1.await(), data2.await(), data3.await())
        .flatMap { it }

    println("非同期で取得したデータを平坦化: $allData")
}

このコードでは、まずasync関数を使用して非同期に3つのリストを取得しています。

その後、await関数を用いて非同期処理の結果を待ち、取得したデータをflatMapで平坦化しています。

結果として、出力は[1, 2, 3, 4, 5, 6, 7, 8, 9]となります。

○サンプルコード10:flatMapを活用した実世界の応用例

実際のアプリケーション開発においても、flatMap関数は多岐にわたるシチュエーションで活用されます。

ここでは、APIから非同期にデータを取得し、そのデータを変換してUIに表示する一連の処理の例を表しています。

import kotlinx.coroutines.*

data class User(val id: Int, val name: String, val hobbies: List<String>)

fun main() = runBlocking {
    val users = async { fetchUsers() }
    val hobbies = users.await()
        .flatMap { it.hobbies }

    println("全ユーザーの趣味一覧: $hobbies")
}

suspend fun fetchUsers(): List<User> {
    // APIからユーザー情報を非同期に取得する想定
    return listOf(
        User(1, "Taro", listOf("読書", "映画")),
        User(2, "Hanako", listOf("料理", "旅行")),
        User(3, "Jiro", listOf("ゲーム", "読書"))
    )
}

上記のコードでは、非同期にAPIからユーザー情報を取得し、取得したユーザーの趣味をflatMapで平坦化して一覧として出力しています。

実行結果、趣味の一覧[読書, 映画, 料理, 旅行, ゲーム, 読書]が得られます。

●flatMapを使う際の注意点と対処法

KotlinでflatMap関数を使用する際には、いくつかの注意点が存在します。

ここでは、それらの注意点とそれに対する対処法を詳細に解説していきます。

○無限ループの回避

flatMap関数を使用する際、誤った使い方をすると、プログラムが無限ループに陥るリスクがあります。

特に、flatMapの中で元のリストを参照するような処理を行うと、意図しない無限ループが発生することが考えられます。

例えば、次のサンプルコードでは、flatMap内で元のリストnumbersを参照し続けるため、無限ループが発生します。

val numbers = listOf(1, 2, 3)
val result = numbers.flatMap { numbers }
println(result)

このコードは、numbersリストを平坦化し続けるため、終了することなく実行が続けられます

このような無限ループを回避するためには、flatMapの中で元のリストを直接参照することを避ける必要があります。

○性能面での考慮点

flatMap関数は、内部的に新しいリストを生成する処理を行います。

このため、大量のデータを扱う場合や、頻繁にflatMapを呼び出す場合は、パフォーマンスへの影響を考慮する必要があります。

例として、次のサンプルコードでは、1万回のflatMap処理を行っています。

val numbers = (1..10000).toList()
val result = numbers.flatMap { listOf(it, it + 1) }
println(result.size)

このコードを実行すると、resultのサイズは2万となります。

しかし、このように大量のデータを扱う場合、メモリやCPUのリソースを大きく消費する可能性があります。

そのため、flatMapを使用する際は、データの量や処理の回数を意識して、効率的なコードを心掛ける必要があります。

●カスタマイズ方法

KotlinのflatMap関数は、非常に強力なツールであり、多くの処理をサポートしていますが、場合によっては、特定のニーズに合わせてカスタマイズすることが求められることもあります。

ここでは、flatMap関数をカスタマイズする方法と、その実例について詳しく解説していきます。

○自分だけのflatMap関数の作成

Kotlinでは、拡張関数を利用して、既存のクラスに新しい関数を追加することができます。

この機能を利用して、独自のflatMap関数を作成することも可能です。

例えば、flatMapを利用して、特定の条件を満たす要素だけを取り出し、その要素に対して何らかの変換を行いたい場合、次のようなカスタムflatMap関数を作成することができます。

fun <T, R> List<T>.customFlatMap(transform: (T) -> Iterable<R>, predicate: (T) -> Boolean): List<R> {
    return this.filter(predicate).flatMap(transform)
}

val numbers = listOf(1, 2, 3, 4, 5)
val result = numbers.customFlatMap({ listOf(it, it * 2) }) { it % 2 == 0 }
println(result)  // 出力: [2, 4, 4, 8]

このコードのcustomFlatMap関数では、transformで指定された変換関数と、predicateで指定された条件関数を用いて、リストの要素をフィルタリングし、変換しています。

上記の例では、偶数の要素だけを取り出し、その要素とその2倍の値を新しいリストに追加する処理を行っています。

その結果、出力として[2, 4, 4, 8]が得られます。

まとめ

KotlinのflatMap関数は、リストやその他のコレクション内の要素を効率的に変換、結合し、新しいコレクションを生成するための強力なツールです。

このガイドを通じて、その基本的な使用方法から応用例、そして注意点やカスタマイズ方法に至るまで、flatMapに関するさまざまな知識を深めることができたかと思います。

初心者から中級者までの読者が、flatMapの真価を理解し、日常のプログラミングに役立てることができるようになったことを期待しています。

また、Kotlinの拡張関数の機能を利用して、既存の関数を自由にカスタマイズし、自分のニーズに合わせて最適化する方法にも触れました。

Kotlinはその柔軟性と強力な関数群で、多くのプログラマーに支持されています。

flatMap関数もその一つであり、日常のタスクを効率的に処理するための優れたツールとなっています。

この記事があなたの参考になれば幸いです。