3-13:cloneを注意してオーバーライドする
要点
Cloneable/Object.clone()は「浅いフィールドコピー」を行い、継承や可変フィールドで簡単に破綻するので、clone() を公開するなら正しく実装して深いコピーや例外処理を行う。ただし多くの場合はコピーコンストラクタ/静的ファクトリを使う方が安全で分かりやすい。
なぜcloneは扱いが難しいか
-
Cloneable はマーカーインタフェース:実際のコピー処理は Object.clone() による。
-
Object.clone() は**フィールドごとのビットコピー(shallow copy)**を行う — mutable なフィールドは同じ参照を共有してしまう。
-
Object.clone() はコンストラクタを通さずにオブジェクトを生成する(初期化ルールを飛ばす)。
-
継承階層で各クラスが clone を「協力」して実装しないと不整合が起きる(super.clone() を呼ぶべきか、どう深いコピーするか等)。
-
例外(CloneNotSupportedException)の扱いや返り値の型(covariant return)など細かい実装上の注意が多い。
悪い例
public class Point {
int x, y;
@Override
public Object clone() { // 警告:返り値は Object、例外未処理
return new Point(x, y); // 手作りコピーはまだマシだが慣習に反する
}
}
問題点:
- clone() のシグネチャが標準と違う(public Object clone() は古く、例外処理がない/互換性の問題)。
- mutable なフィールド(配列、リスト、他のオブジェクト)を浅くコピーしてしまうと共有が起きる(バグ)。
- さらに典型的に危険なのが、super.clone() を呼ばずに new でインスタンスを作る実装や、Cloneable を実装していないのに Object.clone() に依存する実装など。
良い例:安全に clone() を実装するパターン
シンプルな不変・浅コピーで許されるケース(final クラス)
public final class Point implements Cloneable {
private final int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
@Override
public Point clone() {
try {
return (Point) super.clone(); // shallow copy で十分(不変なので安全)
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // 起きないはず
}
}
}
- クラスが final でフィールドが不変なら super.clone()(浅いコピー)で問題ない。CloneNotSupportedException は起きないので AssertionError に変換するのが一般的。
mutable フィールド(深いコピーが必要な場合)
public class Person implements Cloneable {
private String name;
private int[] tags; // mutable
public Person(String name, int[] tags) {
this.name = name;
this.tags = tags;
}
@Override
public Person clone() {
try {
Person p = (Person) super.clone(); // shallow copy
p.tags = tags == null ? null : tags.clone(); // deep copy of mutable field
return p;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
ポイント:
- super.clone() でまず浅いコピー(同じクラスの正しい型を得る)を得る。
- その後、自分のクラスが持つ 可変フィールド を個別にコピーして「深いコピー」を作る。
- 例外は通常 AssertionError に置き換える。
継承が絡むともっと難しい
- サブクラス・スーパークラスがそれぞれ clone() を正しく協力して実装しないと危険。
- 設計上、クラス階層に clone を公開するのは難しい(各クラスが clone を理解して協調する必要あり)。
→ つまり継承を考慮するなら、多くの開発者は clone() を避ける。
例外処理とシグネチャ(推奨)
- CloneNotSupportedException はチェック例外なので、clone() を public にする場合は通常キャッチして AssertionError(起きないはず)に変換しておく。
- 返り値は covariant return を使って具体クラス型を返す(public Person clone())と使いやすい。
なぜ copy constructor / static factory を推すのか(Effective Java の推奨)
- コピーコンストラクタ(new Person(other))や静的ファクトリ(Person.copyOf(other))は 分かりやすく堅牢。
- コンストラクタ/ファクトリならコンストラクタの通常の初期化ルールが走る(clone はコンストラクタを通らない)。
- 継承に対しても扱いやすく、例外処理も単純。
→ 可能なら clone ではなくコピーコンストラクタ/static factory を使うべき。
例(コピーコンストラクタ):
public class Person {
private String name;
private int[] tags;
public Person(Person original) {
this.name = original.name;
this.tags = original.tags == null ? null : original.tags.clone();
}
}
まとめ
- clone() は正しく実装するのが難しい(浅いコピー、継承、例外の扱い、コンストラクタを経ない生成)。
- やむを得ない場合(パフォーマンスや既存 API との互換が理由)には、super.clone() を使ってから mutable フィールドを個別に深コピーし、CloneNotSupportedException は AssertionError に変換するパターンを使う。
- 可能ならコピーコンストラクタや静的ファクトリを使う(これが推奨)。
すぐ使える実装テンプレート(コピーコンストラクタ版と clone() 版)
推奨:コピーコンストラクタ(読みやすく安全)
public class Foo {
private final int[] data;
public Foo(Foo original) {
this.data = original.data == null ? null : original.data.clone();
}
}
必要で clone を実装するなら(最小限の安全パターン)
public class Foo implements Cloneable {
private int[] data;
@Override
public Foo clone() {
try {
Foo f = (Foo) super.clone();
f.data = data == null ? null : data.clone();
return f;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}