A. プログラミングを簡単にするため。
例えば、文字列型 String のデータが変更可能だったとする。
val key1 = "Apple"
val key2 = "Banana"
val set = SortedSet(key1, key2) // "Apple", "Banana"の順に並び替えを維持するデータ構造
// もしkey1の内容を以下の用に書き換えられるとしたら。。。
key1(0) = "Z" // key1は"Zpple"になる
// ここでSortedSetの中身はどうなっている? "Banana", "Zapple"の順番になっていてほしいが。。。
keyの値が外部で変化すると、SortedSetの中で保管しているkeyの並び順も更新する必要がある。 これに対処する方法として、
などが考えられる。
1番目の方法はひどく実装が大変になる。keyの値の変更を探知するObserverをkey毎に用意し、Stringが更新されるたびに並び順を変更する。もし、マルチスレッドプログラミ ングを行っている場合、SortedSetへのアクセスとSortedSetの内容の更新が衝突しないように排他制御を行う必要がある。2番目の方法は簡単だがデータのコピーのコストが重い。3番目の方法になるようでは、SortedSetのようにO(log n)でデータを検索できるデータ構造を使う意味を失う。
また、このようなコードをデバッグするのは困難を極める。 global変数(プログラムのどこからでも変更できる)を用いたコードが今日では衰退しているのは、 変数の内容の変化を起こすコードと、その変化のタイミングを管理するのが大変だったことによる。
Stringをimmutableにすればよい。
immutableにすることで、上記のようなことで悩む必要はなくなる。実際、 Scala/JavaのStringはimmutableになっている。また、SortedSet などの実装はimmutableなデータが格納されることを前提にしており、 性能のためにデータのコピーを避け、文字列へのリファレンスのみを格納している。 parallel/concurrent programmingにおいても、StringやSortedSetの内容がimmutableであることが保証されていると、ロックなどを取得する必要がなくコードの性能が良くなる。さらに、コードの実行の度に動作が違うなど、発見が困難かつ再現しにくいという深刻なタイプのバグに悩まされなくなる。
Scalaではclassでデータ構造を作成する際も、パラメータはすべてimmutableにし、初期化を必ず行うように強制できる。
class Book(val id:Int, val title:String, val publisher:String)
val b = new Book(1, "Programming in Scala", "Artima Press")
Javaではimmutableであることに注意しないと、以下のようなコードを書いてしまう。
// このような書き方は避けたい
class Book {
public int id;
public String title;
public String publisher;
}
Book b = new Book();
b.id = 1;
b.title = "Programming in Scala";
// publisherの情報を設定するのを忘れてしまった!!
System.out.println(b.publisher); // NullPointerExceptionが発生
Javaで安全にクラスの初期化を行えるようにするには以下のようにする。
class Book {
// finalを付けると、初期化時以外変更不能な変数になる
public final int id;
public final String title;
public final String publisher;
public Book(int id, String title, String publisher) {
this.id = id;
this.title = title;
this.publisher = publisher;
}
}
Scalaでは、immutableなデータを好んで使ってもらえるよう配慮されており、以下の一行で済む。
class Book(val id:Int, val title:String, val publisher:String)
より安全にするには、nullかどうかのチェックも入れると良い。
class Book(val id:Int, val title:String, val publisher:String) {
// クラスの初期化時に実行されるコード
if(title == null || publisher == null)
sys.error("null is passed as an argument")
}
JavaではBeans(データベースやJSONなどのデータをもとに、クラスを初期化する)などを使う場合、 止むを得ず上記のようにpublicなフィールドを使って、 安全でない書き方をすることがある。Builder patternを使うなど、 いくつか初期化の安全性を確保する方法があるが、楽な書き方とは言いがたい。