あるクラスで clone()
メソッドをサポートするときには、Cloneable
インタフェースを implements
した上で clone()
メソッドをオーバライドする必要があります。
public MyClass(MyClass obj)
) の導入で済ませられないか検討する。Cloneable
を implements
したクラスを拡張するケース以外では、コピーコンストラクタやコピーファクトリを作成した方がシンプルに実装できることが多い。clone()
を実装するクラスでは、implements Cloneable
と宣言し、自分自身のクラス型のオブジェクトを返す public な clone()
メソッドを実装するclone()
メソッドの実装では super.clone()
を使用し、シャロー・コピーで問題となるフィールドがある場合は、そのフィールドをディープ・コピーするCloneable
インタフェースを implements
しているクラスは、clone()
メソッドの呼び出しをサポートしていることを示します(CloneNotSupportedException
例外をスローせずに、正しくクローン処理が行われるということ)。
clone()
メソッドは Cloneable
インタフェースで定義されているのではなく、実際には Object
クラスで定義されている protected
メソッドです。
サブクラスで Cloneable
メソッドが implements
されているかどうかによって、この Object#clone()
の振る舞いが変化するように実装されています。
class Object {
...
protected Object clone() throws CloneNotSupportedException {
if (!(this instanceof Cloneable)) {
throw new CloneNotSupportedException("Class " + getClass().getName() +
" doesn't implement Cloneable");
}
return internalClone(); // シャロー・コピーを実施
}
private native Object internalClone();
}
具体的には、Cloneable
インタフェースの implements
の有無によって、Object#clone()
の動作が下記のように変化します。
このように親クラスの振る舞いを変化させるためにインタフェースを使用するという設計は極めて特殊であり、Cloneable
インタフェースが嫌われている理由のひとつになっています。
Object (Java Platform SE 8) に、clone()
は下記のような結果を想定すると記述されています。
x.clone() != x
(クローンしたオブジェクトは異なるメモリ領域に確保されているということ)x.clone().getClass() == x.getClass()
(クローンしたオブジェクトは同じ型であること)x.clone().equals(x)
(クローンしたオブジェクトは論理的に同じ値を持つこと)2 と 3 は絶対要件ではないと記述されているので、clone()
メソッドの振る舞いがどうあるべきかは、最終的にはそのクラスの仕様次第ということになります。
「クローン」すると言うからには、通常は上記の条件をすべて満たすように実装されていて欲しいところですが、equals()
メソッドを正しく実装するのはなかなか骨の折れる作業ですので、3 に関してはサポートしないケースもあるでしょう(equals()
が実装されていない場合は、デフォルトで同じ参照かどうかを調べるだけの振る舞いになりますので、3 の評価結果は偽 (false) となります)。
clone()
メソッドを実装する場合、外部から呼び出しを可能にするために、可視性は public にします。
Object#clone()
の実装では、オブジェクトのフィールドがシャロー・コピーされるようになっているため、プリミティブな型の値や、immutable なオブジェクトへの参照しかフィールドとして持たないクラスの clone()
メソッドは、単純に super.clone()
を呼び出すことによって完結できます(Object#clone()
の実行にたどり着くために、すべてのスーパークラスが super.clone()
を使用して実装しているという前提が必要です)。
public class Stack implements Cloneable {
private int x = 0; // Primitive 型
private int y = 0; // Primitive 型
private String text = "hello"; // Immutable なオブジェクト
...
@Override public TextView clone() {
try {
return (TextView) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // Should not happen
}
}
}
また、J2SE 1.5 からは共変戻り値型の仕組みが取り入れられたため、戻り値は自分自身のクラスの型にキャストしてから返すことができます。共変戻り値型というのは、戻り値の型を、その型のサブタイプにしてオーバライドすることのできる仕組みです。これが導入される前は、clone()
の戻り値はすべて Object 型で定義しなくてはならず、呼び出し側でキャストして使用しなければいけませんでした。
immutable でないオブジェクトへの参照をフィールドで保持しているようなクラスで clone()
メソッドをサポートする場合は、それらのフィールドをディープ・コピーする処理を記述する必要があります。
public class Stack implements Cloneable {
private int size = 0;
private Object[] elements;
...
@Override public Stack clone() {
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone(); // コピー元と同じ参照を持たないようにする
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // Should not happen
}
}
}