【Java】不変オブジェクトの作り方　参照の隠蔽と防衛的コピー

[前回の記事](http://qiita.com/chooyan_eng/items/f0cb376584ee28452dee)で、Javaにおける不変オブジェクトのメリットを簡単に説明しました。

前回はStringやjava.timeパッケージ内の時間クラスなど、JavaのAPIとして用意されているクラスを使って説明しましたが、今度は自作クラスを不変オブジェクトにするための工夫について説明してみます。

# 参照の隠蔽

まず大事なのは「参照を隠す」ということです。
言葉にすると難しそうですが、要はインスタンス変数をprivateにして、不必要なsetterは定義しないようにしましょうね、ということです。

以下は、 ```Person.java``` という、参照の隠蔽を実践したクラスです。

```java:Person.java
public class Person {
  private String name;

  public Person(String name) {
    this.name = name;
  }
}
```

このPersonクラスを使うメインクラスは ```ImmutableSample.java``` とします。

```java:ImmutableSample.java
public class ImmutableTest {
  public static void main(String args[]) {
    String name = "Taro";
    Person taro = new Person(name);
  }
}
```

Personクラスの使用者であるImmutableTestクラスは、Personクラスの ```name``` フィールドにアクセスする術を持たず、またname変数もStringクラスがそもそも不変であるため、```taro``` インスタンスの保持する値（ ```name``` 変数に格納された ```"Taro"``` という文字列）を変更することはできません。

インスタンス変数がString型だけであれば、これで不変クラスは完成です。

# 防衛的コピー

では次に、先ほどのPersonクラスに「誕生日」を表すDate型の ```birthday``` 変数を追加してみます。

```java:Person.java
public class Person {
  private String name;
  private Date birthday;

  public Person(String name, Date birthday) {
    this.name = name;
    this.birthday = birthday;
  }

  public String toString() {
    return String.format("%sさんの誕生日は%sです。", name, new SimpleDateFormat("yyyy/MM/dd").format(birthday));
  }
}
```

先ほどの「参照の隠蔽」で説明した通り、このクラスはインスタンス変数が全て ```private``` でsetterメソッドも定義されていません。しかし、このクラスは不変ではありません。以下のように使うことで、```birthday``` を書き換えることができるためです。

```java:ImmutableTest.java
public class ImmutableTest {
  public static void main(String args[]) throws ParseException {
    // Taroインスタンスの生成
    String name = "Taro";
    Date birthday = new SimpleDateFormat("yyyyMMdd").parse("19870929");
    Person taro = new Person(name, birthday);

    System.out.println(taro.toString());

    // birthdayを外から変更
    birthday.setTime(System.currentTimeMillis());
    System.out.println(taro.toString());
  }
}
```

```
Taroさんの誕生日は1987/09/29です。
Taroさんの誕生日は2016/09/29です。
```

これでは、mainメソッドの ```birthday``` 変数が持つ参照値があとの処理に引き回されでもしたら、いつTaroの誕生日が変わってしまうか予測できない状況になってしまいます。

これを防ぐための工夫が「防衛的コピー」です。Personクラスを以下のように書き換えます。

```java:Person.java
public class Person {
  private String name;
  private Date birthday;

  public Person(String name, Date birthday) {
    this.name = name;
    this.birthday = new Date(birthday.getTime()); // birthdayインスタンスのコピーを保持する
  }

  public String toString() {
    return String.format("%sさんの誕生日は%sです。", name, new SimpleDateFormat("yyyy/MM/dd").format(birthday));
  }
}
```

この状態で、ImmutableTest.javaのソースコードはそのまま変えずに実行すると、

```
Taroさんの誕生日は1987/09/29です。
Taroさんの誕生日は1987/09/29です。
```

となり、いくら外からTaroの持つ変数を書き換えようとしてもできない状態を作り出すことができます。

また多くの場合、Taroの誕生日を参照するためのgetterメソッドを用意する必要がります。このとき単純にgetterメソッドを用意してしまうと、せっかくコンストラクタでコピーした ```birthday``` の参照値を使用者に渡してしまうことになります。

```java:Person.java
public Date getBirthday() {
  return birthday;
}
```

このような場合も防衛的コピーを使います。

```java:Person.java
public Date getBirthday() {
  return new Date(birthday.getTime());
}
```

これで、不変でない ```Date``` 型のフィールドを持つ```Person```クラスを不変にすることができました。

注）
日付クラスについてはJava8から不変性をもつクラスがいくつも追加されていますので、実際は「そもそもDateクラスを使わない」というのが正しいのですが、今回は説明のためにあえてDateクラスを使用しています。

# リストを持つクラスを不変にする

最後に、Personに休暇日を表す ```vacations``` を追加してみます。

```java:Person.java
public class Person {
  private String name;
  private Date birthday;
  private List<Date> vacations;

  public Person(String name, Date birthday, List<Date> vacations) {
    this.name = name;
    this.birthday = new Date(birthday.getTime());
    this.vacations = new ArrayList<>(vacations);
  }

  public List<Date> getVacations() {
    return new ArrayList<>(vacations);
  }
}
```

```birthday``` と同じように防衛的コピーを使ってみました。これで大丈夫でしょうか。

```java:ImmutableTest.java
public class ImmutableTest {
  public static void main(String args[]) throws ParseException {

    SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");

    String name = "Taro";
    Date birthday = format.parse("19870929");
    List<Date> vacations = new ArrayList<>();
    vacations.add(format.parse("20161003"));
    vacations.add(format.parse("20161005"));
    vacations.add(format.parse("20161010"));
    Person taro = new Person(name, birthday, vacations);

    SimpleDateFormat printFormat = new SimpleDateFormat("yyyy/MM/dd");
    for (Date vacation : taro.getVacations()) {
      System.out.println(printFormat.format(vacation));
    }

    taro.getVacations().get(0).setTime(System.currentTimeMillis());

    for (Date vacation : taro.getVacations()) {
      System.out.println(printFormat.format(vacation));
    }
  }
}
```

```
2016/10/03
2016/10/05
2016/10/10

2016/09/29 //書き換えられた！
2016/10/05
2016/10/10
```

書き換えられてしまいました。
```taro``` インスタンスの ```vacations``` インスタンス自体は不変ですが、その中の要素はImmutableTest.java の ```vacations``` 変数が持つ要素と同じインスタンスであるため、このままでは外から書き換えることが可能です。

それを防ぐためには、リスト内の要素についても防衛的コピーを適用する必要があります。
また、getterメソッドも同様にします。

```java:Person.java
public class Person {
  private String name;
  private Date birthday;
  private List<Date> vacations;

  public Person(String name, Date birthday, List<Date> vacations) {
    this.name = name;
    this.birthday = new Date(birthday.getTime());

    // リストを新しく作り、中の要素についてもひとつずつ防衛的コピーをする。
    this.vacations = new ArrayList<>();
    for (Date vacation : vacations) {
      this.vacations.add(new Date(vacation.getTime()));
    }
  }

  public List<Date> getVacations() {
    // インスタンスのvacationsインスタンスだけでなく、中の要素もひとつずつ防衛的コピーをしてから返却する。
    List<Date> vacations = new ArrayList<>();
    for (Date vacation : this.vacations) {
      vacations.add(new Date(vacation.getTime()));
    }
    return vacations;
  }
}
```

```ImmutableTest.java``` はそのまま変えずに実行すると、以下のようになります。

```
2016/10/03
2016/10/05
2016/10/10

2016/10/03 //書き換えられない！
2016/10/05
2016/10/10
```

これで、リストを渡す場合でも不変性を実現することができました。

# まとめ

ここまで、オブジェクトを不変にするための工夫を紹介してきましたが、実際はもしかしたら完全に不変なオブジェクトを作るという機会はあまりないかもしれません。開発者がお互いにコミュニケーションをとれる状況なのであれば、不用意にインスタンスの内容を書き換えない、参照値を引き回さない、といった「使う側」のコーディングで対処できる場合があるからです。また、防衛的コピーは不変性を実現する一方で、getするたびに ```new``` が呼ばれてしまう、というデメリットもあります。

Effective Javaにも繰り返し書かれているのですが、このようなテクニックが常に正しいとは限りません。要件や状況に応じて使い分ける必要があります。

とはいえ、Javaで他のクラスにインスタンス（の参照値）を渡すことによってどのような危険性が生じるのかを知る、という意味で、不変オブジェクトの考え方を知っておくのはとても良い勉強になるかと思います。