Kotlinでは、不変性とは通常、オブジェクトが作成された後にその状態を変更できないことを意味します。これは、val(変更不可)プロパティの使用や、内部データを変更するためのメソッドを持たないコンストラクタでのみ初期化されるクラスの作成を通じて実現されます。
Javaとは異なり、Kotlinはdata classを介して不変クラスを作成するための便利なメカニズムや、デフォルトで変更不可のList、Set、Mapといったコレクションを提供しています。しかし、Kotlinでは基本的なコレクションの不変性はインターフェースのレベルでのみ保証され、コレクションオブジェクト自体はMutableListなどにキャストすることで変更可能です。
適切な不変性の例:
// Data classとvalは不変性を保証します。 data class User(val name: String, val age: Int) val user = User(name = "Ivan", age = 30) // user.name = "Sergey" // コンパイルエラー
不変性が破られるコードの例:
class User(var name: String, var age: Int) val user = User("Ivan", 30) user.name = "Sergey" // オブジェクトの変更が可能
注意点:
valフィールドを持つdata classでも、参照型のフィールドが内部状態を変更する場合は完全な不変性を保証しません。val scores: List<Int>)とともにvalのみを使用してください。
valプロパティだけを持つdata classのオブジェクトは完全に不変ですか?
答え:いいえ、プロパティが変更可能なオブジェクト(例:val items: MutableList<Int>)である場合、内部状態を変更することができます。
例:
data class Group(val members: MutableList<String>) val group = Group(mutableListOf("Tom", "Jerry")) group.members.add("Spike") // 内部状態が変更されます
物語
プロジェクトでは、
valプロパティのタイプがMutableListの不変data classが使用されていました。一人の開発者はオブジェクトが変更できないと期待していましたが、別の開発者がコレクションに新しい要素を追加しました。これにより、データの不整合と、2つのスレッドが同じリストを同時に変更する際に発生する見つけにくいバグが生じました。
物語
アプリケーションのレイヤー間で変更可能なコレクションを持つクラスのインスタンスを渡す際、一方のレイヤーが内容を変更し、他のレイヤーに通知しませんでした。これにより、UIは発生した変更を知らず、無効な元データで動作してしまいました。
物語
開発者は、Kotlinの
Listは常に不変であると誤って思っていました。実際には、内部ではJavaから渡された実装(ArrayList)が使用されており、リストを変更しようとするとデータの予期しない損失やデータベースとの操作時にバグが発生しました。