ProgrammingミドルAndroid開発者

Kotlinではコレクションとオブジェクトの不変性(イミュータビリティ)がどのように実現されており、実践がJavaとどのように異なるか?

Hintsage AIアシスタントで面接を突破

回答

問題の歴史:

Javaでは、コレクションの不変性はCollections.unmodifiableList()のようなラッパーを介して、手動でimmutableクラスを設計(finalフィールド、セッターの不在)することで達成されます。Androidやサーバー開発の実践ではこれが使いづらく、不便になり、スレッドの安全性やデータの予測可能性が失われました。

問題:

Javaのコレクションはデフォルトで安全ではなく、常にオブジェクトへの参照を取得して変更することが可能です。これにより、不変性の管理が困難になり、特にマルチスレッド環境では微妙なバグが発生します。

解決策:

Kotlinは最初からread-onlyとmutableコレクションの分離を設計し、真の不変オブジェクトの作成がval、data class、およびmutable接頭辞のないList/Set/Mapを使うことで簡単になりました。これに加えて、copy()メソッドを使ったコピーの利用も可能です。

コード例:

val nums: List<Int> = listOf(1, 2, 3) // 不変リスト // nums.add(4) // コンパイルエラー val user = User(name = "Alex", age = 30) val updated = user.copy(age = 31) // 不変マップの例 val state: Map<String, Int> = mapOf("a" to 1, "b" to 2)

重要な特徴:

  • read-onlyコレクションを変更しようとした場合、コンパイルエラーが発生します。
  • Kotlinのvalは参照の不変性を保証しますが、オブジェクトの不変性は保証しません。
  • 真の不変性には、read-onlyインターフェースだけを使用し、内部状態の変更を禁止してください。

トリッキーな質問。

valは内部オブジェクトの絶対的な不変性を保証しますか?

いいえ!valは変数が他のオブジェクトを指さないことしか保証しませんが、オブジェクトのフィールドは変更可能です(内部プロパティがvarの場合)。

read-onlyコレクションからミュータブルオブジェクトを取得できますか?

はい — コレクションがミュータブルオブジェクトを含んでいる場合、read-onlyインターフェースのコレクションにもかかわらず、その状態を変更することができます。

data class User(var name: String) val users: List<User> = listOf(User("Vasya")) users[0].name = "Petya" // 許可される

List<Int>をas?でMutableList<Int>に変換するとどうなりますか?

コンパイルはできますが、元の型がread-onlyインターフェースだった場合、実行時にClassCastExceptionが発生します。

一般的な間違いやアンチパターン

  • valをオブジェクトの絶対的な不変性の保証と見なすこと
  • "immutable"コレクション内のミュータブルオブジェクトへの参照の操作
  • read-onlyコレクションをミュータブルに変換すること

実生活の例

ネガティブケース

Androidアプリケーションはval List<User>で状態を保持し、それを不変であると考えています。開発者の1人がuser.nameを変更します。別のフラグメントで更新されたリストが予期しない不整合なデータを表示 — バグがリリース時に発生します。

長所:

  • コレクションの高速操作

短所:

  • 変更管理の喪失
  • 隠れた可変性

ポジティブケース

valフィールドのみを持つimmutable data classを使用し、内部オブジェクトへのアクセスをフィルタリングし、copy()を通じて状態を更新します。

長所:

  • 高い予測可能性とスレッドセーフ性
  • 予期しない副作用の不在

短所:

  • 時にはより多くのアロケーションとコピーが必要
  • すべてのサードパーティライブラリが真の不変オブジェクトを提供するわけではない