KotlinのmapNotNullについて

採用はこちら

mapNotNull は Kotlin のコレクション API の中でも特に使用頻度が高く、「Kotlin らしい null 安全なコード」を書くための中核的な関数です。

一方で、mapfilterNotNull との違い、Sequence との関係、パフォーマンス面については曖昧な理解のまま使われていることも少なくありません。

この記事では、公式仕様に沿った正確な説明と、実務での適切な使い分けに重点を置いて解説します。

目次

mapNotNull とは何か

mapNotNull は、次の2つの処理を 1つの操作として行う関数です。

  1. 各要素を別の型に変換する(map)
  2. 変換結果が null のものを結果から除外する

代表的な定義(Iterable に対するもの)は、概念的に次のように表せます。

fun <T, R : Any> Iterable<T>.mapNotNull(
    transform: (T) -> R?
): List<R>

※ 実際には Iterable 以外にも SequenceArray などに対する mapNotNull が用意されています。

このシグネチャで重要な点

  • transformnull を返してよい
  • ただし型パラメータ RAny を継承(= non-null 型)
  • そのため、結果は null を含まない List<R> になる

この設計により、「不要・失敗・該当なし」を null として表現しつつ、最終的なコレクションは 型レベルで null 安全になります。

基本的な使用例

条件に合う要素だけを変換する

val numbers = listOf(1, 2, 3, 4, 5)

val result = numbers.mapNotNull { n ->
    if (n % 2 == 0) n.toString() else null
}

// result = ["2", "4"]

このコードでは、

  • 偶数 → 文字列に変換
  • 奇数 → null を返す(結果には含めない)

というルールを、1つのラムダにまとめています。

map + filterNotNull との違い

同様の処理は、次のようにも書けます。

numbers
    .map { if (it % 2 == 0) it.toString() else null }
    .filterNotNull()

違いの整理(Iterable の場合)

観点map + filterNotNullmapNotNull
意図の明確さ処理が分かれる「変換して不要なら捨てる」が一目で分かる
実装上の挙動中間リストが作られる1回の走査で完結
型安全性一時的に nullable常に non-null

List などの Iterable に対しては
map { ... }.filterNotNull() は通常、

  1. List<R?> を生成
  2. それを filterNotNull()List<R> に変換

という2段階処理になります。

一方、mapNotNull変換と null チェックを1回の走査で行うため、

  • コードが簡潔になりやすく
  • 中間コレクションを作らずに済む

という利点があります。

ただし、要素数が少ない場合は差が誤差になることも多く、常に高速になると考えるべきではありません

Sequence と mapNotNull

asSequence() を使った場合、評価モデルが変わります。

list.asSequence()
    .map { ... }
    .filterNotNull()
    .toList()

この場合、

  • 処理は 遅延評価
  • 中間コレクションは作られない

という特徴があります。

Sequence におけるポイント

  • map { ... }.filterNotNull()
  • mapNotNull { ... }

どちらも要素が要求されるたびに処理されるため、中間リストの有無という差はありません

Sequence で差が出るとすれば、

  • ラムダの構造
  • 分岐の書き方
  • インライン展開や最適化の影響

といった実装レベルの要因になります。

そのため Sequence を使う場合は、

パフォーマンスよりも「処理の意図が読みやすいか」

を基準に選ぶのが現実的です。

戻り値が non-null になる理由

mapNotNull の結果が List<R?> ではなく List<R> になるのは、
型パラメータ RAny 制約があるためです。

  • transformR? を返す
  • null は結果から除外される
  • 残った要素は必ず non-null

このため、後続処理では

result.forEach {
    println(it.length) // safe
}

のように、!!?. を使う必要がありません。

これは Kotlin の null 安全を型で保証する思想をそのまま体現した設計です。

実務でよく使われるパターン

nullable なプロパティの抽出

data class User(
    val id: Int,
    val email: String?
)

val emails = users.mapNotNull { it.email }
  • email == null のユーザーは自動的に除外
  • 非常に頻出する実務パターン

パース・変換に失敗した要素を除外

val inputs = listOf("10", "20", "abc", "30")

val numbers = inputs.mapNotNull { it.toIntOrNull() }

「失敗時は null を返す API」と mapNotNull は非常に相性が良く、エラーハンドリングを簡潔に書けます。

検索・取得結果をまとめる

val users = ids.mapNotNull { id ->
    repository.findUserById(id)
}
  • 見つからない → null
  • 見つかったものだけを集約

という意図を自然に表現できます。

使うべきでないケース

null 自体に意味がある場合

mapNotNull は null を 必ず捨てるため、

  • null を後続処理で区別したい
  • 状態(成功/失敗)を保持したい

場合には不向きです。そのようなケースでは、

  • map
  • Result
  • sealed class

などで状態を明示した方が安全です。

条件と変換を明確に分けたい場合

list
    .filter { 条件 }
    .map { 変換 }

この方が意図を読み取りやすいケースもあり、常に mapNotNull が最適とは限りません

まとめ

  • mapNotNull は「変換」と「null の除外」を同時に行う関数
  • 結果が non-null であることが型レベルで保証される
  • Iterable では 中間コレクションを避けやすく、簡潔に書ける
  • Sequence では 中間コレクションの差はなく、可読性重視で選ぶ
  • 「null = 失敗・不要」という設計と特に相性が良い

以上、KotlinのmapNotNullについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次