mmutableとimmutable
immutable
とmmutable
は対になっており、それぞれメリットとデメリットがあります。
immutable
は「不変」、mmutable
は「可変」の意味を持っています。
「可変」の方が、つまりmmutable
の方が変更しやすくて良さそうに思いますが、その変更しやすさのゆえバグの温床になります。
配列と文字列
一般に配列は操作可能で、文字列は操作不可能です。
ただしここでの操作は、変数の操作(再代入)ではなく、データそのものの操作に関してです。
下記が例です。
プリミティブであるString
に関しては、"my name"の操作ができません。
class User {
private val name: String = "my name"
private val friends: MutableList<String> = mutableListOf("matuo", "tanaka", "hamada")
fun updateName() {
name.append("hoge") // nothing method
}
fun appendFriend() {
friends.add("sakata") // ok
}
}
ただしvarを利用することで、再代入が可能になります。
class User {
private var name: String = "my name"
fun updateName() {
name = "hoge" // ok
}
}
ちなみにkotlinでは、mmutableとimmutableのlistがそれぞれ存在しています。
collectionはimmutableで、mutableListはmmutableです。
immutableなクラスとmmutableなクラス、その問題点
次に、mmutableなクラスを考えてみます。
メンバ変数をvarで定義したので、変数が再代入可能という意味でmmutableになっています。
class User(name: String, age: Int) {
var name: String
var age: Int
init {
this.name = name
this.age = age
}
}
同一インスタンスのメンバ書き換え
次に下記のコードを実行し、同一インスタンスに対して名前の書き換えを行います。
val user = User("hamada", 35)
println("${user.hashCode()}") // 598446861
println("${user.name}: ${user.age}") // hamada: 35
user.name = "tanaka"
println("${user.hashCode()}") // 598446861
println("${user.name}: ${user.age}") // tanaka: 35
同一ハッシュ(インスタンス)に対して、名前の書き換えが行われています。
これは問題でしょうか。
確かに問題ではあるものの、名前の書き換えを行うことは実際に起き得るので「書き換えが簡単に行われている」ことが確かに問題です。
変数の代入によるメンバ書き換え
次に同じ例を用いて下記のコードを実行します。
val user = User("hamada", 35)
println("${user.name}: ${user.age}") // hamada: 35
val user2 = user
user2.name = "asada"
println("${user2.name}: ${user2.age}") // asada: 35
println("${user.name}: ${user.age}") // asada: 35
val user2 = user
によって参照先が渡され、user2
の変更がuser
に影響してしまっています。
新しいユーザーを作成したつもりが、mmutable
であるが故に意図しない変更が起こりうるのです。
解決策: immutableなクラスを生成する
今回の本題のimmutableなクラスに関してです。
kotlin
のdata class
でメンバをval定義します。
これだけでひとまずimmutableなクラス定義は完了です。
data class User(val name: String, val age: Int)
val user = User("hamada", 35)
user.name = "tanaka" // Val cannot be reassigned
新しいtanaka
ユーザーを作成したい場合はインスタンスをさらに作成する必要があります。
data class User(val name: String, val age: Int)
val user1 = User("hamada", 35)
val user2 = User("tanaka", 25)
immutableなクラスの値の更新について
メンバにval
をつけて、再代入を不可にすることでimmutabilityを獲得しました。
しかし、※1楽観的更新でユーザーの情報を更新したい場合にはどうすれば良いのでしょうか。
data class
にはcopy
メソッドが用意されているのでそちらを利用しましょう。
var user = User("hamada", 35)
println("${user.name}: ${user.age}") // hamada: 35
var newUser = user.copy(name = "tanaka")
println("${user.name}: ${user.age}") // hamada: 35
println("${newUser.name}: ${newUser.age}") // tanaka: 35
data class
は他にもequals
メソッドで等値比較(参照比較ではなく)ができるので、宣言的UIにはとても便利です。
flutter
ではfreezed
パッケージを用いて、簡単にimmutability
を獲得できます。
(コンパイルは伸びます)
※1 楽観的更新
サーバーに通信して、帰ってきた情報からインスタンスを再生成すればimmutableでも更新できますが通信前にimmutableクラスを更新したい場合です。
まとめ
immutabiltyは大事です。