プログラム
Kotlinのイミュータブル、ミュータブルとはなんなのか? どんなメリットがあるのか?
Kotlinはイミュータビリティ -Immutability- (変更不可能性)、ミュータビリティ-Mutability- (変更可能性)の概念がある言語です。
イミュータブル -Immutable- (変更不可能)なオブジェクト、ミュータブル -Mutable- (変更可能)なオブジェクトと呼んだりし、プログラムの中に出てくるオブジェクトを呼び分けたりもします。
それではこのイミュータビリティ、ミュータビリティとはどういった概念なのか?
そして開発する上でどのようなメリット、利点を僕たちにもたらしてくれるのでしょうか?
上記の疑問に加え、その使い方、使い分け方を説明していきます。
参考にしたページは以下になります。
Collections: List, Set, Map
6 Benefits of Programming with Immutable Objects in Java
Method parameters and mutable variables
イミュータビリティ、ミュータビリティの概要
イミュータビリティとは、日本語で変更不可能性や不変性といいます。
そしてイミュータブルオブジェクトとは、そのオブジェクトが変更不可能だということを表したオブジェクトになります。
ミュータビリティとは、日本語で変更可能性や可変性といいます。
そしてミュータブルオブジェクトとは、そのオブジェクトが変更可能だということを表したオブジェクトになります。
まずはミュータブルオブジェクトについて説明したいと思います。
ミュータブルなオブジェクトは、イミュータビリティ、ミュータビリティの概念に馴染みのない人でもすぐにわかるオブジェクトです。
Javaでの変数を頭に思い浮かべてください。
例えば以下のような変数はミュータブルなオブジェクトになります。
// Javaのコード
String mutableString = "ミュータブルなString";
mutableString = "ミュータブルオブジェクトなので変更可能である";
ミュータブルとは変更可能という意味があります。
つまり、ミュータブルなオブジェクトとは、そのオブジェクトを上書きできるということになります。
なので、Javaでよく見かける変数はミュータブルなオブジェクトになります。
それではイミュータブルオブジェクトについて考えてみましょう。
上記でミュータブルなオブジェクトを理解した今では、全く難しくない概念です。
イミュータブルとは変更不可能という意味があります。
つまり、イミュータブルオブジェクトとは、そのオブジェクトを上書きできないということになります。
Kotlinのイミュータブルなオブジェクトは、イミュータブルに設定した変数やプロパティに一度値を代入した後、値を上書きすることができません。
イミュータブルに設定した変数やプロパティを初期化した後は、それ以降変更することができないということになります。
もし変更しようとした場合、コンパイル時に変更できないというエラーが発生します。
一見、馴染みのない人からすると、このイミュータブルなオブジェクトは非常に扱いづらいもののように感じられるかもしれません。
なんたって変数やプロパティを一度初期化してしまうと、その後変更することができないですからね。
そんな扱いづらそうなイミュータブルなオブジェクトですが、一度使い慣れてしまうと、二度と手放せなくなるくらい重要な要素となってきます。
次の項 目では、イミュータブルなオブジェクトのメリット、利点を紹介していきます。
イミュータブルオブジェクトのメリット、利点
それではイミュータブルなオブジェクトにはどのようなメリット、利点があるのでしょうか?
見ていきましょう。
イミュータブルなオブジェクトはスレッドセーフ
イミュータブルなオブジェクトはスレッドセーフになります。
なぜなら、一度値が割り当てられた後は値を変更できないため、あらゆるスレッドからどんな形で参照しても同じ結果になるからです。
つまり、あらゆる非同期エラーが発生しないということになります。
注意点としては、あるクラスのインスタンスの参照をイミュータブルにしたとしても、そのクラスにミュータブルなプロパティがある場合、スレッドセーフになりません。
以下のようなクラスはスレッドセーフにならないので注意してください。
/**
* ミュータブルなプロパティを持つクラス。
*
* @property mutableProperty ミュータブルなプロパティ。
* Kotlinではvarと記述するとミュータブルなオブジェクトになる。
*/
class MutablePropertyIncludedClass(var mutableProperty: String)
/**
* クラスの参照はイミュータブルだが、プロパティがミュータブルなので書き換えできてしまう例。
*/
fun main(args: Array<string>) {
// イミュータブルな変数として宣言しているため、クラスの参照は再代入できないようになっている
// Kotlinではvalと記述するとイミュータブルなオブジェクトになる
val mutablePropertyIncludedClass = MutablePropertyIncludedClass("mutableString")
// しかし、mutablePropertyはミュータブルなオブジェクトで定義されているため、書き換えができてしまう
// そのため、このクラスはスレッドセーフではない
mutablePropertyIncludedClass.mutableProperty = "overridable"
}
上記の例では、MutablePropertyIncludedClassにあるmutablePropertyがミュータブルなオブジェクトととして定義されています。
そのため、mutablePropertyを変更することができてしまうため、MutablePropertyIncludedClassはスレッドセーフにはなりません。
ちなみに、MutablePropertyIncludedClassを保持している変数mutablePropertyIncludedClassはイミュータブルなオブジェクトとして宣言しているため、新しい参照を代入することはできません。
上記のクラスをスレッドセーフにしたい場合、mutablePropertyに定義しているvarをvalに変更する必要があります。
valで定義することにより、mutablePropertyがイミュータブルになるため、上記のクラスはスレッドセーフになります。
イミュータブルオブジェクトはMapのキーやSetの要素に適している
イミュータブルなオブジェクトは、一度作られると変更できないため、MapのキーやSetの要素に適しています。
例えば、Mapのキーとしてあるクラスを使用したとします。
そのクラスがミュータブルなプロパティを持っている場合、以下のような問題が発生します。
/**
* Mapのキーとなるクラス。
*
* @property keyInt ミュータブルで定義されたプロパティ。
*/
calss MapKey(var keyInt: Int)
/**
* 複雑な処理がこの中で行われる
*/
fun main(args: Array<string>) {
val mapKey = MapKey(1)
val valueString = "mapKey is 1."
val beProblemMap = mapOf(mapKey to valueString)
// 複雑な処理が色々あるとする
// ...
// なんらかの理由でmapKeyの値を書き換えてしまう
mapKey.keyInt = 2
// また複雑な処理
// ...
// keyIntが1で来るという想定の処理だが、keyIntが変更されてしまっているため、以下の処理が上手く走らない
beProblemMap.forEach { (key, value) }
if (key.keyInt == 1) println(value)
}
}
ミュータブルなプロパティがあるクラスをMapのキーにすると、上記のようにMapのキーにしているクラスのプロパティが変更されてしまったために思わぬ不具合が発生する可能性が出てきます。
イミュータブルなオブジェクトを使用すれば、プロパティを書き換えられることがないため、このような問題を避けることができます。
イミュータブルなオブジェクトのメリットの一つとなります。
イミュータブルなオブジェクトを使用することは論理的な設計をもたらす
イミュータブルなオブジェクトは一度値を代入すると、その後変更することができません。
その規則に沿ってプログラムを作成するということは、効率的かつ論理的にプログラムを作成することにつながります。
イミュータブルな変数やプロパティは一度代入すると変更ができません。
プログラムの処理の流れの中で変更不可能なオブジェクト、変更可能なオブジェクトを使い分けることは、それだけで俯瞰した、マクロな視点でプログラムの構造を設計できます。
そしてイミュータブルとミュータブルの使い分けを実践してすぐに感じるのは、ミュータブルなオブジェクトを使う場面が想像以上に少ないということです。
経験上、プログラムの処理の約70%は、イミュータブルなオブジェクトで運用できます。
ミュータブルなオブジェクトを使用しないということは、それだけ処理の中で値の代入をする場面を減らすことにもつながります。
そのため、プログラムの効率をよくし、把握しやすいコードになり、論理的な設計をすることにつながります。
また、イミュータブルなオブジェクトを使用することを意識し始めると、今までミュータブルで宣言してきた無駄な変数やプロパティを減らしたいという思考にもつながります。
ミュータブルなオブジェクトを減らすということは、同じような意味を持つミュータブルなオブジェクトを一つのイミュータブルなオブジェクトに置き換えることになることが多いため、無駄な処理を減らすことにつながります。
そのため、プログラム全体のコード量が減り、メンテナンス性、処理の軽さなどにつながってきます。
イミュータビリティな思考は、本当にメリットが大きいです。
イミュータブルなオブジェクトを使用するとより簡単に並列処理を実装できる
イミュータブルなオブジェクトはスレッドセーフなため、並列処理でそのオブジェクトが競合することがありません。
そのため、並列処理をより簡単に実装することができます。
例えば、あるクラスに入っているプロパティの値を並列処理で使い回す場合に、値が変わらないことを保証するためにオブジェクトをロックする必要がありません。
そのため処理を簡潔に書くことができます。
以下の例はある値を保持したクラスを複数のスレッドで使用しています。
そのクラスにあるプロパティはイミュータブルなオブジェクトで、値が変更されないことが保証されているため、オブジェクトをロックするという処理をしなくても 問題ありません。
package kotlinsorce
import java.util.Random
import kotlin.concurrent.thread
/**
* 並列処理で使用されるクラス。
*
* @property immutableInt 並列処理で計算に使用されるプロパティ。
* イミュータブルで定義されているため、スレッドセーフになる。
*/
class ImmutableClass(val immutableInt: Int)
/**
* イミュータブルなオブジェクトを並列処理で安全に使う例。
*/
fun main(args: Array<string>) {
// 並列処理で使用するリストの作成
val calcList = listOf(100, 10, 20, 35, 40)
val calcList2 = listOf(3, 9, 21, 49, 52)
// 以下のクラスは並列処理で使われる
// このクラスのimmutableIntはイミュータブルなプロパティとして宣言されているため、どのスレッドから使用されても変更されることがないことが保証されている
val immutableClass = ImmutableClass(3.14)
// 以下の二つのスレッドはimmutableClass.immutableIntを使用するが、immutableClass.immutableIntはイミュータブルなため、値が変更されてしまうということを考えずに処理ができる
thread {
calcList.forEach {
val random = Random()
Thread.sleep(random.nextInt(10).toLong())
println("$it * immutableClass.immutableInt = ${it * immutableClass.immutableInt}")
}
}
thread {
calcList2.forEach {
val random = Random()
Thread.sleep(random.nextInt(10).toLong())
println("$it * immutableClass.immutableInt = ${it * immutableClass.immutableInt}")
}
}
}
プログラム内部の状態が矛盾せず、エラーの発生を削減できる
イミュータブルなオブジェクトを使えば使うほど、プログラム内での些細なミスが減り、エラー発生の確率を下げます。
イミュータブルなオブジェクトは、値を代入後変更されないことが保証されているので、些細な思い違いでの値の書き換えなどが発生することがありません。
そのため、内部ロジックに矛盾が発生しません。
イミュータビリティの概念に慣れるまではしんどいですが、慣れると心強い味方であり、余計な心配を考える必要から解放してくれる最高の機能となります。
イミュータブルなオブジェクトのデメリット
いいプログラミングの実践として、可能な限りイミュータブルなオブジェクトを使用するようにするべきです。
この推奨はEffective Javaにて、Joshua Bloch氏が以下のように述べています。
クラスはそれらをミュータブル(変更可能)にするよい理由がない限り、イミュータブル(変更不可能)にするべきです。
もしクラスがイミュータブルにできないなら、可能な限り、その変更可能な性質を制限するべきです。
上記で述べられているとおり、できる限りオブジェクトはイミュータブルなオブジェクトにするべきです。
ですが、イミュータブルなオブジェクトは変更することができません。
なので、イミュータブルなクラスの一部のプロパティを変更したい場合、新しいインスタンスを作成した上で 、変更したいプロパティに値を代入し、それ以外のプロパティには古いインスタンスから値をコピーしてくるという処理が発生することになります。
そのため、実行時のパフォーマンスを重視する必要がある場面では冗長になり、パフォーマンスの低下につながる可能性があります。
それがイミュータブルなオブジェクトのデメリットと言えます。
例えば、ゲームのプログラミングは実行速度を重視する場面が多々あります。
そういう場合、あえてミュータブルなオブジェクトを使用し処理速度を向上する必要があるかもしれません。
どこまでパフォーマンスを優先し、どこまで安全性を取るか、そのさじ加減が難しい一面もデメリットと言えるかもしれません。
イミュータブルオブジェクトとミュータブルオブジェクトの使い方
Kotlinでは変数やプロパティを宣言する際にイミュータブル、ミュータブルのどちらで使用するかを一緒に記述する必要があります。
valを変数名の前に記述するとイミュータブル(変更不可能)なオブジェクトになります。
varを変数名の前に記述するとミュータブル(変更可能)なオブジェクトになります。
val immutableInt: Int = 1
var mutableInt: Int = 2
valで宣言した変数に値を再代入するとコンパイルエラーになります。
varで宣言した変数は何度でも再代入することが可能です。
immutableInt = 2 // コンパイルエラーになる
mutableInt = 3
mutableInt += 5 // 何度でも変更可能
Kotlinのコレクションは、変数やプロパティの宣言と同じようにイミュータブルかミュータブルを使用者に選ばせるようになっています。
例えばListのインスタンスを作成する場合、listOf関数を使用してインスタンスを作成する必要があります。
listOfを使用するとListインターフェースを継承したクラスのインスタンスが返ってきます。
Listインターフェースはaddやremoveなどが実装としてないため、イミュータブルなオブジェクトとなります。
val immutableInts: List<int> = listOf(1, 3, 5, 2, 10)
immutableInts.add(11) // add関数がないためコンパイルエラーになる
それではListに要素を追加したり削除したりしたい場合どうすればいいのでしょうか?
Kotlinには、要素の追加や削除をすることができるミュータブルなListが用意されています。
ミュータブルなListを使用するにはMutableListを使用します。
mutableListOf関数を使用すると、MutableListインターフェースを継承したクラスのインスタンスが返ってきます。
val mutableInts: MutableList<int> = mutableListOf(1, 3, 5, 2, 10)
mutableInts.add(11) // MutableListにはaddの実装があるためコンパイルエラーにならない
同じようにSetやMapにもMutableSetやMutableMapが用意されており、イミュータブルとミュータブルを使い分けさせるようになっています。
注意点としては、コレクションのイミュータブル、ミュータブルの選択とは別に、そのインスタンスの参照もイミュータブル、ミュータブルを選択する必要があります。
インスタンスの参照がイミュータブル、ミュータブルなのと、コレクションのクラス自体がイミュータブル、ミュータブルなのは別なので注意してください。
// 要素の追加、削除ができるMutableListのインスタンスを取得しているが、その参照はイミュータブルで宣言している
val mutableMessages: MutableList<string> = mutableListOf("a", "b", "c")
// ミュータブルなListなのでaddできる
mutableMessages.add("d")
// インスタンスの参照はイミュータブルなので再代入できないのでコンパイルエラーになる
mutableMessages = mutableListOf("e", "f", "g")
また、Listの要素として指定したクラスのプロパティにミュータブルなプロパティがある場合、イミュータブルなListに追加したとしても、そのプロパティは書き換えられてしまう可能性があることに注意してください。
書き換えられえたくない場合、そのクラスのプロパティを全てイミュータブルで定義してください。
Kotlinのコレクションにはイミュータブル、ミュータブルの概念がある
上述しましたが、Kotlinのコレクションはイミュータブルかミュータブルを使用者に選ばせるようになっています。
Kotlinの公式ページでは以下のように述べられています。
コレクションが編集できるタイミングを正確に制御することは、バグを排除することに役立ち、いいAPIの設計の役に立ちます。
Kotlinはこのような考えのもと、コレクションにもイミュータブルかミュータブルを使用者に選ばせるようにしているようです。
そんなKotlinのコレクションのイミュータブル、ミュータブルの使い方を見ていきましょう。
// 各コレクションのインスタンス取得方法
val stringList: List<string> = listOf("test1", "test2", "test3")
val mutableStringList: MutableList<string> = mutableListOf("test1", "test2", "test3")
val stringSet: Set<string> = setOf("test1", "test2", "test3")
val stringMutableSet: MutableSet<string> = mutableSetOf("test1", "test2", "test3")
val intAndStringMap: Map<int, string=""> = mapOf(1 to "test1", 2 to "test2", 3 to "test3")
val intAndStringMutabkeMap: MutableMap<int, string=""> = mutableMapOf(1 to "test1", 2 to "test2", 3 to "test3")
List、Set、Mapがイミュータブルなコレクションになり、Mutable〜がミュータブルなコレクションになります。
各コレクションは〜Of関数を使用してインスタンスを取得します。
各コレクションの取得関数は、JavaのようにCollection<T>、Iterable<T>を継承したクラスを返すようになっています。
KotlinのList、Set、Mapは読み取り操作のみを提供するインターフェースとなっています。
つまり、List、Set、Mapにはadd、putやremoveなどのコレクションの要素を変更するような実装がないため、イミュータブルになっています。
mapOf、mutableMapOfでの注意点として、Kotlinの公式サイトで以下のように述べられています。
パフォーマンス重視でないコードでのMap作成は、単純な構文mapOf(a to b, c to d)が使用できます。
mapOf、mutanleMapOfを使用するとパフォーマンスが悪くなるようです。
効率的な使用方法に関しては記載がありませんでした。
mapOf、mutanleMapOfを使用する際は注意してください。
空のコレクションを取得したい場合は以下のようにします。
val emptyList: List<string> = emptyList<string>()
val emptyMutableList: MutableList<string> = mutableListOf<string>()
val emptySet: Set<string> = emptySet<string>()
val emptyMutableSet: MutableSet<string> = mutableSetOf<string>()
val emptyMap: Map<int, string=""> = emptyMap<int, string="">()
val emptyMutableMap: MutableMap<int, string=""> = mutableMapOf<int, string="">()
// 各インスタンスの取得は以下のように省略もできる
val emptyMutableList = mutableListOf<string>()
val emptyMutableList: MutableList<string> = mutableListOf()
Listのようなイミュータブルなコレクションを使用する際、以下のような使い方をするとコレクションの中身が変化することに注意してください。
val numbers: MutableList<int> = mutableListOf(1, 2, 3)
val readOnlyView: List<int> = numbers // MutableListをListで参照する
println(numbers) // [1, 2, 3]を表示する
numbers.add(4)
println(readOnlyView) // 参照元のMutableListが変更されたため、readOnlyViewでも[1, 2, 3, 4]と表示される
readOnlyViewはミュータブルなリストを参照しているため、そのリストの変更によって内容が変化してしまいます。
思わぬところでこのようなことが起きないように気をつける必要があります。
そうならないためにも、必要ない場面では安易にMutableListなどを使用せず、Listなどを使用するよう心がけましょう。
そうすることにより、イミュータブルなコレクションとして扱うことができます。
今のところ、listOfメソッドは配列のリストを使用して実装されているようです。
将来的には、よりメモリ効率のよい完全なイミュータブルなコレクションの型を返すようにするようです。
ミュータブルなコレクションをイミュータブルなコレクションとして参照する方法
あるクラスにあるミュータブルなコレクションを、参照する時はイミュータブルなコレクションとして参照したい場合があります。
そういう場合、以下のようにできます。
/**
* ミュータブルなコレクションをバッキングプロパティとして持つクラス。
* itemsプロパティを使用すると、現在保持されているミュータブルなコレクションをイミュータブルなコレクションとして返す。
*
*/
class Controller {
/**
* ミュータブルなコレクションを保持するバッキングプロパティ。
*/
private val _items = mutableListOf<string>()
/**
* 現在このクラスが保持しているイミュータブルなリストを返す。
*/
val items: List<string> get() = _items.toList()
}
上記のように、保持しているコレクションの変更はクラスのみでされ、その時点で保持している内容を取得したい場合、イミュータブルなリストとして取得することができます。
toList拡張関数は、_itemsのリストをそのまま複製し、イミュータブルなリストとして返します。
そのため、返されたリストは変更されないことが保証されます。
イミュータブルなコレクションは共変性になる
イミュータブルなコレクションはジェネリックでいう共変性になります。
つまり、仮にBaseというクラスがあり、それを継承したDerivedというクラスがあった場合、List<Derived>をList<Base>に割り当てることができます。
/**
* List<derived>をList<base>に割り当てる。
*/
fun main(args: Array<string>) {
// List<base>はList<derived>の共変性になるため、割り当てることができる
val derivedList = listOf(Derived())
val baseList: List<base> = derivedList
baseList.forEach {
it.foo()
}
}
/**
* 基本クラス。
*/
open class Base() {
fun foo() {
println("Base class")
}
}
/**
* 基本クラスを継承したクラス。
*/
class Derived : Base() {
fun bar() {
println("Derived class")
}
}
Kotlinの関数の引数は全てイミュータブルになる
Kotlinの関数の引数は全てイミュータブルになります。
これはKotlinを使用する上でのメリットでもあります。
多くの場合、関数の引数は変更しないほうが自然なことが多いです。
そして、不具合のいくつかは、引数が変更されないことを前提とした実装の中で間違って引数の値を変更してしまい起こります。
そのため、関数の引数がイミュータブルになるKotlinはそういった不具合の発生を防ぐというメリットがあります。
英文ですが、以下のディスカッションで、このKotlinの機能によって不具合が30%減ったと言っている人もいます。
30%の不具合の削減はかなりの量だと思います。
それくらい、Kotlinの関数の引数がイミュータブルになるというこの機能はメリットがあるということです。
Method parameters and mutable variables
クラスを引数にした場合、クラスの参照だけがイミュータブルになることに注意してください。
例えば、ミュータブルなプロパティを持ったクラスを引数にした場合、そのクラス内のミュータブルなプロパティは変更できてしまいます。
もしミュータブルにする必要のないプロパティだったなら、イミュータブルなプロパティに変更することをおすすめします。
イミュータブルオブジェクトとミュータブルオブジェクトの使い分け方
イミュータブル、ミュータブルなオブジェクトについて理解を深め、使い方を学びました。
それでは、実際にどのような場面でその二つの機能を使い分けたらよいのでしょうか?
いくつかの例を記載しますので参考にしてください。
イミュータブルにしたほうがいいパターン
まずはイミュータブルにしたほうがいいパターンを見ていきましょう。
プログラムの中で使用するデフォルト値はイミュータブルにする
何らかのデフォルト値をデータベースやAndroidでいうプリファレンスに保存しているとします。
その値を変数やプロパティに格納して処理を行う場合、イミュータブルにしたほうがいいです。
例えば、メール管理アプリを作っていたとします。
メール作成時、署名が設定されていればメールの最後の行に署名を追加するという仕様だとします。
その署名の値はイミュータブルにするべきです。
設定された署名はユーザーによって変更されるまで変更されることはないので、メール作成時の処理の中ではむしろイミュータブルな変数やプロパティで保持しておくほうがいいです。
データ を保持するクラスのプロパティはイミュータブルにする
何らかの情報を保持するためだけのクラスを作成する場面は多いかと思います。
そういうクラスのプロパティはイミュータブルにするほうがいいです。
例えば、ユーザー情報を保持するクラスがあったとします。
ユーザーID、ユーザー名、ユーザーの生年月日、ユーザーの住所などがそのクラスには保持できます。
このようなデータを保持するクラスはイミュータブルにするほうがいいです。
こういった情報はユーザーが変更するまでプログラムで変更することはありません。
そのため、イミュータブルにしていたほうがプログラム内にバグが潜む可能性が少なくなります。
ユーザーのデータが変更された場合は、そのクラスの変更のないプロパティをコピーし、変更があったプロパティは変更後の値でインスタンスを生成するという風にします。
Kotlinでは、data classでクラスを定義している場合、必要な値のみを書き換えたインスタンスの生成が簡単に取得できます。
import java.util.Calendar
/**
* ユーザー情報を保持する。
*/
data class UserInfo(val id: Int, val name: String, val birthday: Calendar, val address: String)
/**
* data classのコピー機能を使用した例。
*/
fun main(args: Array<string>) {
// data classのインスタンスを作成する
val birthday = Calendar.getInstance()
birthday.set(3000, 12, 31)
val naokichi = UserInfo(0, "naokichi", birthday, "地球")
// 新しいインスタンスに住所以外の情報を全てコピーする
val newNaokichi = naokichi.copy(address = "宇宙")
println(newNaokichi)
}
基本的に変数やプロパティはイミュータブルにする
いくつかイミュータブルなオブジェクトを扱う例を記載しましたが、イミュータブルの扱いに慣れない間は全ての変数、プロパティはイミュータブルにするくらいでちょうどいいと思います。
全ての変数、プロパティをイミュータブルで扱っている内にミュータブルにしないとどうにもならない処理が出てくるはずです。
その処理で使用するオブジェクトこそがミュータブルにすべきオブジェクトということになります。
また、上記のように全てのオブジェクトをイミュータブルにするようにすると、意外とほとんどの処理がイミュータブルなオブジェクトでも問題ないことに気づくはずです。
慣れてくると、プログラムの設計段階で、どのオブジェクトをイミュータブル、ミュータブルにすればいいかという広い視野での設計ができるようになります。
慣れるまでの間は、変数、プロパティはイミュータブルで定義することをおすすめします。
ミュータブルにしたほうがいいパターン
それではミュータブルにしたほうがいいパターンを見ていきましょう。
処理の分岐を判断するためのBooelanはミュータブルのほうがいい場合がある
Booleanの変数はミュータブルになる可能性が高いオブジェクトの一つだと思います。
例えば、ある処理を通った場合はAの処理を行い、それ以外はBの処理を行うという場合、その分岐の制御にBooleanを使用することがあると思います。
以下のような例の場合、処理の分岐に使用するBooleanはミュータブルにする必要があります。
あなたはあるアプリ内で入力項目のチェックをする処理を作成しています。
いくつかの入力項目のチェックがあり、入力項目の内の一つでもチェックを成功で通過しなかった場合、エラーメッセージが表示され、チェックを通過しなかった項目は全て赤枠で表示される仕様になっているとします。
そのチェックを通過したかどうかに使用するBooleanはミュータブルにする必要があります。
/**
* ユーザー情報が正しく入力されているかを確認する。
*
* @param targetUserInfo 確認するユーザー情報。
* @return チェックに通過したかどうかを返す。
*/
fun checkUserInfo(targetUserInfo: UserInfo): Boolean {
var isSuccess = true
// ユーザー名が空白の場合、エラー
if (targetUserInfo.name.equals("")) {
// setErrorにテキストボックスを渡すことで、そのテキストボックスが赤枠で囲まれる
setError(nameEditText)
isSuccess = false
}
// 住所が空白の場合、エラー
if (targetUserInfo.address.equals("")) {
setError(addressEditText)
isSuccess = false
}
// 入力エラーがある場合、メッセージを表示
if (isSuccess == false) {
println("赤枠の項目を見直してください。")
}
return isSuccess
}
画面に表示するコンポーネントの座標などの値はミュータブルのほうがいい
例えばペイントアプリを作成していたとします。
ペン、消しゴムなどの切り替えのボタンを画面上に表示するという仕様だとします。
その切り替えボタンはロングタップすると画面上の好きな位置に移動することができるとします。
その場合、その切り替えボタンの画面上の座標を保持することになると思います。
その座標はミュータブルにする必要があります。
イミュータブルにした場合、座標を管理するインスタンスを何度も再作成する必要がでてき、非効率的な処理が発生してしまいます。
このように、値がすぐに変わってしまう可能性があるオブジェクトにはミュータブルを設定するほうが効果的な場合があります。
まとめ
僕はイミュータビリティという概念をKotlinに触れて初めて知りましたが、イミュータブルなオブジェクトを使って思ったのが、プログラム上で使用するプロパティ、変数のほとんどがイミュータブルでも問題ないということです。
イミュータブルとミュータブルの使い分けを始める前は、きっと面倒な機能なのだろうな、と思っていましたが、慣れた今となってはプログラムを作成する上で必須の機能となりつつあります。
全ての言語にこの機能が追加されればいいなと思うくらい、重宝しています。
また、イミュータブルとミュータブルを意識することにより、プログラムのより深い設計を練ることになるため、 結果として単純かつ綺麗な設計になることもわかりました。
イミュータブル、ミュータブルに触れて、確実に、自分のプログラムの設計力が上がったのを実感しています。
この記事のほかに、Kotlinのメリットについてまとめた記事もあります。
よろしければ合わせてご参照ください。