mapNotNull は Kotlin のコレクション API の中でも特に使用頻度が高く、「Kotlin らしい null 安全なコード」を書くための中核的な関数です。
一方で、map や filterNotNull との違い、Sequence との関係、パフォーマンス面については曖昧な理解のまま使われていることも少なくありません。
この記事では、公式仕様に沿った正確な説明と、実務での適切な使い分けに重点を置いて解説します。
mapNotNull とは何か
mapNotNull は、次の2つの処理を 1つの操作として行う関数です。
- 各要素を別の型に変換する(map)
- 変換結果が
nullのものを結果から除外する
代表的な定義(Iterable に対するもの)は、概念的に次のように表せます。
fun <T, R : Any> Iterable<T>.mapNotNull(
transform: (T) -> R?
): List<R>
※ 実際には Iterable 以外にも Sequence、Array などに対する mapNotNull が用意されています。
このシグネチャで重要な点
transformは null を返してよい- ただし型パラメータ
RはAnyを継承(= 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 + filterNotNull | mapNotNull |
|---|---|---|
| 意図の明確さ | 処理が分かれる | 「変換して不要なら捨てる」が一目で分かる |
| 実装上の挙動 | 中間リストが作られる | 1回の走査で完結 |
| 型安全性 | 一時的に nullable | 常に non-null |
List などの Iterable に対しては、map { ... }.filterNotNull() は通常、
List<R?>を生成- それを
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> になるのは、
型パラメータ R に Any 制約があるためです。
transformはR?を返す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 を後続処理で区別したい
- 状態(成功/失敗)を保持したい
場合には不向きです。そのようなケースでは、
mapResultsealed class
などで状態を明示した方が安全です。
条件と変換を明確に分けたい場合
list
.filter { 条件 }
.map { 変換 }
この方が意図を読み取りやすいケースもあり、常に mapNotNull が最適とは限りません。
まとめ
mapNotNullは「変換」と「null の除外」を同時に行う関数- 結果が non-null であることが型レベルで保証される
- Iterable では 中間コレクションを避けやすく、簡潔に書ける
- Sequence では 中間コレクションの差はなく、可読性重視で選ぶ
- 「null = 失敗・不要」という設計と特に相性が良い
以上、KotlinのmapNotNullについてでした。
最後までお読みいただき、ありがとうございました。










