immutable object
”immutable object”。
日本語で言うと「不変オブジェクト」。
これは、「一度値を設定したら値を変更できないクラス/オブジェクト」のことです。
いつどこで使う?
動作中に値を変えられては困るクラスを定義する場合が挙げられます。
例えば、とあるデータベースへの接続情報を保持するクラスがあるとします。
データベースへの接続情報は、システムを起動し一度設定したら、(普通)変える必要はありません。
むしろ、途中で誤った設定に変更できたりすると、システムが異常終了したりしてしまいます。
こうした、途中で誤った設定をされたりすると困るクラス/オブジェクトについて、設定を後からいじれないように保障してくれるのが、”immutable object”です。
どうやって作る?
Effective Java によると、大きく5つの手順があります。
- setter(mutator)を作らない
- クラスを拡張できないようにする
- 変数を final にする
- 変数を private にする
- 変数を外部からいじれないようにする
以下に具体例を示します。
使用するクラス
以下は、名前と背番号を保持する、何らかの「選手」を表す JavaBeans クラスです。
これを、immutable object へ改造していきます。
/** * 選手を表す JavaBeans クラス。 */ public class Player { // fields // 名前 private String name; // 背番号 private int number; // constructors public Player() { } // getter public String getName() { return this.name; } public int getNumber() { return this.number; } // setter public void setName(String name) { this.name = name; } public void setNumber(int number) { this.number = number; } }
(1)setter(mutator)を作らない
setter のことを、mutator とも言います。
ちなみに mutator とは、「変えるもの」という意味です。(mutate:変える)
まず、この mutator(setter)を削除します。
(2)クラスを拡張できないようにする
クラスが拡張できると、サブクラスから値を変更される恐れが出てきます。
これを避けるため、クラスを final で定義します。
(3)変数を final にする
変数を一度設定したら変更できないようにするために、変数を final で定義します。
また、変数を final にする場合、final 変数をコンストラクタで設定する必要があります。
そのため、final 変数を設定できるコンストラクタを定義します。
(4)変数を private にする
変数が外部から参照できるようだと、変数が操作されかねません。
そのため、変数を private で定義します。
(5)変数を外部からいじれないようにする
変数を private かつ final にしても、変数が参照型かつ mutable な場合、値を変更できることがあります。(例えば java.util.Date 型など)
そういう変数については、”defensive copy”というテクニックを使います。
(”defensive copy”についてはこちら。)
ちなみに今回の例では、これに該当する変数がないため(int は参照型ではない&String は参照型だが immutable)、特にやることはありません。
変更後のクラス
/** * 選手を表す JavaBeans クラス。 */ //(2)クラスをfinalで定義 public final class Player { // fields //(3)変数をfinalで定義 //(4)変数をprivateで定義 // 名前 private final String name; // 背番号 private final int number; // constructors //(3)final変数を設定するコンストラクタを定義 public Player() { this("", 0); } public Player(String name, int number) { this.name = name; this.number = number; } // getter public String getName() { return this.name; } public int getNumber() { return this.number; } //(1)setter(mutator)を削除