The HIRO Says

If you smell what The HIRO is cooking!!!

defensive copy

前回(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点に集約できます。

  1. immutable object とそのクライアントが、同一の参照を持っている。
  2. クライアントが持っている参照は、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”以外にも、以下のような対応策があります。

  1. クライアント側の参照も immutable にする。
  2. javadoc コメントなどで、値を操作しないで!という旨を明記する。

後者は、クライアントの良心・良識にゆだねる方法なので不安定ですが、作りこみが不要なので、費用対効果が期待できることがあります。


6.関連情報

  1. "immutable object"