Effective Java 第 2 版:第 3 章 項目 11
clone
を注意してオーバーライドする
Object.clone
は適切にオーバーライドしないと問題が起こるという話。
clone
はあるオブジェクトのコピーを返すメソッドである。
例えば、GoF のデザインパターンの一つ、“Prototype” パターンで利用されている。
clone
のオーバーライド方法には注意が必要である。
というのは、Object.clone
は、デフォルトではオブジェクトのコピーを返さず、CloneNotSupportedException
を投げるからである。
インタフェース Cloneable
を実装したクラスから Object.clone
を呼ぶことで、そのオブジェクトをフィールドごとにコピーしたオブジェクトを返すようになる。
clone
に対する一般契約
clone
に対する契約は以下の通り。
しかし、必須の契約ではない。
x.clone() != x
はtrue
であるx.clone().getClass() == x.getClass()
はtrue
であるx.clone().equals(x) == true
である- コピー生成時にコンストラクタは呼び出されない
clone
の望ましい処理
final
ではないクラスで clone
をオーバーライドするとき、フィールドが基本データ型もしくは不変オブジェクトならば super.clone
を返せばよい。
それなら、最終的には Object.clone()
が呼び出され、求めるクラスのコピーが得られる。
例えば、以下のようになる。
public class Circle implements Cloneable {
private double x;
private double y;
private double radius;
@Override public Circle clone() {
try {
return (Circle) super.clone();
} catch (CloneNotSupportedException cnse) {
throw new AssertionError(); // 起き得ない
}
}
//...
}
ここで、返り値型が Object
ではなく Circle
でよいのは、共変戻り値型であることによる。
つまり、オーバーライドされたメソッドにおいて、戻り値型のサブクラスを使えるからである。
さて、問題はフィールドに配列や参照型などの可変オブジェクトが存在するときである。
この場合に super.clone()
を返すだけだと、オリジナルのオブジェクトとコピーの間で可変オブジェクトを共有してしまう。
その結果、一方で変更があると、もう片方にその影響が及んでしまう。
このときは、それらのオブジェクトに対して再帰的に clone
を呼び出す。
例えば、以下のようになる。
public class MyQueue implements Cloneable {
private Object[] queue;
private int size;
public MyQueue(int size) {
this.size = size;
queue = new Object[size];
}
@Override public MyQueue clone() {
try {
MyQueue result = (MyQueue) super.clone();
result.queue = queue.clone(); // 再帰的に clone
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
//...
}
ちなみに、もし可変オブジェクトが final
だと、上のような代入ができず、clone
の処理がうまく書けない。
この場合は final
を外す必要があるかもしれない。
最後に、継承されるように設計したクラスについては、そのクライアントに clone
をオーバーライドするか否かの選択肢を残すため、Object
と同様に、protected clone()
をオーバーライドしておく。
@Override protected Foo clone() throw CloneNotSupportedException {
//...
}
clone
の代替案
clone
よりよい方法として、以下のようなコピーコンストラクタか static ファクトリーメソッドによるコピーファクトリーがある。
public Foo(Foo foo)
public static Foo newInstance(Foo foo)