前回(immutable object)、変数を private かつ final にしても、変数が参照型かつ mutable な場合、値を変更できることがあることについて触れました。
今回は、その詳細および対処方法としての”defensive copy”について説明します。
1.具体例
前回作成した「選手」クラスに、java.util.Date 型の「生年月日」を追加してみます。
import java.util.Date; /** * 選手を表す JavaBeans クラス。 */ public final class Player { // fields // 名前 private final String name; // 背番号 private final int number; // 生年月日 private final Date birthday; // constructors public Player() { // 便宜的に new Date(0) とします this("", 0, new Date(0)); } public Player(String name, int number, Date birthday) { this.name = name; this.number = number; this.birthday = birthday; } // getter public String getName() { return this.name; } public int getNumber() { return this.number; } public Date getBirthday() { return this.birthday; } }
生年月日は、private かつ final な変数として定義してあります。
パッと見、特別問題ないように見えますが…
2.発生する問題
(1)コンストラクタで渡した値の操作
Date birthday = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss") .parse("2001/02/03 12:34:56"); Player target = new Player("1", 2, birthday); birthday.setYear(109);
上記の結果、生年月日の値は "2009/02/03 12:34:56" に変わってしまいます。
このように、オブジェクトをコンストラクタへ渡した後にいじられると、private かつ final な変数であっても変更されてしまいます。
(2)getter で取得した値の操作
Player target = new Player( "1", 2, new SimpleDateFormat("yyyy/MM/dd hh:mm:ss") .parse("2001/02/03 12:34:56")); Date newBirthday = target.getBirthday(); newBirthday.setMonth(10);
上記の結果、生年月日の値は "2001/11/03 12:34:56" に変わってしまいます。
このように、getter で渡したオブジェクトをいじられると、private かつ final な変数であっても変更されてしまいます。
3.原因
上述の一連の問題の原因は、次の2点に集約できます。
- immutable object とそのクライアントが、同一の参照を持っている。
- クライアントが持っている参照は、immutable ではないので、操作が可能である。
4.対処方法としての”defensive copy”
上述の問題の対処方法はいくつかあるのですが、ここでは”defensive copy”による対処方法を説明します。
”defensive copy”とは、参照からオブジェクトを複製することで、immutable object とクライアント双方が同一の参照を持たないようにする方法です。
どういうことなのか、具体例で見てみましょう。
(1)コンストラクタで渡した値の操作
元のコンストラクタでは、参照をそのまま変数にセットしています。
this.birthday = birthday;
これを、参照から複製したオブジェクトを渡すようにします。
this.birthday = new Date(birthday.getTime());
こうすれば、immutable object とクライアントとで別々の参照を持つことになるので、クライアントから値をいじれなくなります。
(2)getter で取得した値の操作
元の getter でも、参照をそのままクライアントへ渡しています。
return this.birthday;
これも、参照から複製したオブジェクトを渡すようにします。
return (Date) this.birthday.clone();
5.他の対処方法
”defensive copy”以外にも、以下のような対応策があります。
- クライアント側の参照も immutable にする。
- javadoc コメントなどで、値を操作しないで!という旨を明記する。
後者は、クライアントの良心・良識にゆだねる方法なので不安定ですが、作りこみが不要なので、費用対効果が期待できることがあります。