JavaにもPHPのcloneメソッドのような機能があり、PHPのcloneメソッドのような使い勝手を期待したが、あまり良いものではなかった。使い方が結構特殊だったのでメモを残しておく。
2種類のコピー方法
オブジェクトのコピーにはシャローコピーとディープコピーの2種類があり、cloneメソッドの使い方を学ぶ上では、前提知識として持っておくべきである。以下より、それぞれのコピーについて説明する。
シャローコピー
シャローコピー(浅いコピー)は、コピー元のオブジェクトとコピー先のオブジェクトがメモリ上の同じデータ(インスタンス変数)を参照している状態である。
コピー元のオブジェクトに対してインスタンス変数に変更を加えると、コピー先のオブジェクトが参照しているデータが同じ物なので、コピー先のオブジェクトから見たインスタンス変数も変更されることになる。
つまり、シャローコピーは参照のコピーのみを行う。
ディープコピー
ディープコピー(深いコピー)は、オブジェクトのみのコピーではなく、オブジェクトとメモリ上のデータ(インスタンス変数)の両方をコピーする。
二つのオブジェクトが参照しているデータは別々のものなので、一方のオブジェクトのインスタンス変数に変更を加えても、もう一方のオブジェクトには影響を与えない。
cloneメソッドとは
cloneメソッドとは、オブジェクトのコピーを返すメソッドである。Javaのcloneメソッドはかなり特殊で、Java以外の言語(PHPやRubyなど)のcloneメソッドではディープコピーを提供するが、Javaのcloneメソッドでは、シャローコピーが提供される。しかし、メソッドの使用者がcloneメソッドに対して期待する動作はディープコピーであるため、Javaのcloneメソッドでは、不変オブジェクト(状態が変更されないオブジェクト)のみをシャローコピーとして提供し、可変オブジェクト(状態が変更されるオブジェクト)をディープコピーとして提供出来るように書き換える必要がある。
Javaのcloneメソッドの使い方
Cloneableインターフェースは、Javaの中では異端なインターフェースである。Cloneableインターフェースは、メソッドを含んでいないにも関わらず、Object.clone()のオーバーライドを必要とする(全てのクラスは暗黙的にObjectクラスを継承している)。逆に、Object.clone()は、継承したクラスがCloneableインターフェースを実装(implements)していなければ、実行できず、CloneNotSupportedExceptionをスローする。
cloneメソッドを実装する際のルール
- Cloneableインターフェースを実装(implements)する。
- Object.clone()をオーバーライドし、cloneメソッドをpublic、もしくはprotectedで宣言する。
- cloneメソッドにより生成されたオブジェクトではコンストラクタが実行されない。
- Cloneableインターフェースを実装するクラスのフィールドが、不変オブジェクト(プリミティブ型、String型、またはプリミティブ型のラッパークラス型)の場合は、cloneメソッドの中身はsuper.clone()を呼び出して、その戻り値を返すだけの単純な実装になる。
- Cloneableインターフェースを実装するクラスのフィールドが可変オブジェクト(配列、コレクション、クラス型)を持つ場合は、そのフィールドを複製する実装をcloneメソッドに記述し、更にsuper.clone()を呼び出し、それぞれの戻り値を一緒に返す必要がある。
1つ目~3つ目のルールについては、書いてある通りなのでこれ以上の説明は省くとして、注目して欲しいので4つ目と5つ目である。
4つ目では、不変オブジェクトのみを扱う場合は、シャローコピーのみを行う。その理由は、不変オブジェクトは内部の状態が変わることが無いので、ディープコピーを行う必要が無いためである。
5つ目では、可変オブジェクトを扱う場合は、そのオブジェクトのディープコピーを行う。その理由は、可変オブジェクトをシャローコピーすると、コピー元のオブジェクトとコピー先のオブジェクトがメモリ上の同じデータを参照するため、予想が付かない振る舞いを引き起こしかねないからである。
cloneメソッドの実装例
クラスに不変オブジェクトしかない場合
public class DataBox implements Cloneable {
private int historyNO = 0;
private Integer value = 0;
private String data = "a";
@Override
public DataBox clone() {
try {
return (DataBox)super.clone();
} catch() {
throw new AssertionError();
}
}
}
クラスに可変オブジェクトがある場合
public class SmokyDog implements Cloneable {
private String name = "SmokyDog";
private Date birthDate;
private setBirthDate(Date b) {
this.birthDate = b;
}
@Override
public SmokyDog clone() {
try {
SmokyDog result = (SmokyDog)super.clone(); //ディープコピー
result.setBirthDate(new Date(this.birthDate.getTime()));
return result;
} catch() {
throw new AssertionError();
}
}
}
Javaでは、Cloneableを実装する以外にもオブジェクトのコピーを作成する方法がある。以下の2つがそうなのだが、長くなりそうなので今回は紹介だけに留める。
- コピーコンストラクタ
- コピーファクトリー
参考
Object (Java Platform SE 8 )
書籍 Effective Java
最新Javaコーディング作法 プロが知るべき、107の規約と21の心得
Javaにおけるオブジェクトの複製(clone) – Object#cloneのオーバーライド
シャローコピーとディープコピーの違い – くろの雑記帳