はじめに
Kotlinは「Null安全」が特徴の一つですが、
コレクション(List, Set, Map)になると少し混乱しがちです。
たとえば次の2つ、何が違うか分かりますか?
val list1: List<String?> = listOf("A", null, "B")
val list2: List<String>? = null
一見似ていますが、意味はまったく違います。
この記事では「Null許容コレクション」の型の違い、使い方、注意点を整理します。
型の基本構造
Kotlinでは「コレクション」と「要素」のnull許容が独立しています。
| 型 | 意味 |
|---|---|
List<String> |
nullを含まないリスト |
List<String?> |
要素がnullを許容するリスト |
List<String>? |
リスト自体がnullを許容する |
List<String?>? |
リストも要素もnullを許容する |
例:
val a: List<String> = listOf("A", "B") // ✅ OK
val b: List<String?> = listOf("A", null) // ✅ OK
val c: List<String>? = null // ✅ OK
val d: List<String?>? = listOf(null, "B") // ✅ OK
安全なアクセスパターン
要素が null の場合(List<String?>)
val names: List<String?> = listOf("Tom", null, "Anna")
for (name in names) {
// name は String? 型
println(name?.uppercase() ?: "Unknown")
}
?.や?:を使えば安全に扱える。
リスト自体が null の場合(List<String>?)
val items: List<String>? = getItemsOrNull()
// 安全呼び出し
println(items?.size ?: 0)
// スマートキャスト
if (items != null) {
println("First item: ${items.first()}")
}
リストも要素も null の場合(List<String?>?)
val values: List<String?>? = getValues()
val firstNonNull = values?.firstOrNull { it != null } ?: "default"
null合体演算子(
?:)をうまく使うと安全。
null除去のテクニック
Kotlin標準ライブラリには便利な拡張関数があります。
filterNotNull()
要素のnullを削除して List<T> に変換。
val raw: List<String?> = listOf("A", null, "B")
val clean: List<String> = raw.filterNotNull()
println(clean) // [A, B]
実践例:サーバーAPIレスポンス
data class ApiResponse(
val users: List<User?>? // サーバー都合でnullが混ざる
)
fun handleResponse(res: ApiResponse?) {
val validUsers = res?.users
?.filterNotNull()
?.filter { it.isActive } // 有効ユーザーだけ
println(validUsers ?: emptyList())
}
よくある間違い
| コード | 問題点 |
|---|---|
listOf(null, "A") を List<String> に代入 |
コンパイルエラー |
list!!.size |
NPE発生のリスク |
list.map { it.length } (要素がnull可) |
コンパイルエラーになる (itがString?のため) |
ベストプラクティスまとめ
| ケース | 型 | 推奨操作 |
|---|---|---|
| 要素だけnull可 | List<T?> |
filterNotNull() or ?.
|
| リストだけnull可 | List<T>? |
?.let {} / ?: emptyList()
|
| 両方null可 | List<T?>? |
組み合わせ (?., filterNotNull()) |
| null禁止 | List<T> |
そのままで安全 |
まとめ
- Kotlinでは**「リストがnull」か「要素がnull」か**を明確に分けて型に表現できる
- 安全呼び出し演算子(
?.)とfilterNotNull()を組み合わせれば実用的 - Null許容は「柔軟」だが「複雑」なので、API設計時に意図を型で明示するのがポイント