オブジェクトのクローン(複製)を可能にしたい場合には、そのクラスにCloneable インターフェイスを実装して、cloneメソッドを定義します。
public class A implements Cloneable {
public Object clone() throws CloneNotSupportedException {
Object r = super.clone();
...
return r;
}
}
PMD : ProperCloneImplementation / CloneMethodMustImplementCloneable
CheckStyle : SuperClone
FindBugs : CN: clone method does not call super.clone()
このとき、cloneメソッドの先頭で必ず super.clone() を呼び出すようにします。
こうすることで、このクラスから派生したクラスで clone() を呼び出したときに
必ず Object.clone() が呼び出される事が保証されます。
public class B extends A {
public Object clone() throws CloneNotSupportedException {
Object r = super.clone();
...
return r;
}
}
Aから派生したクラスBで super.clone() を呼び出さないと、A.clone() が呼び出されない為Object.clone() も呼び出されません。
以下のようなクラス構成を考えます。
public class A implements Cloneable {
public Object clone() throws CloneNotSupportedException {
A obj = new A();
...
return obj;
}
}
たまに、このように clone() 内で new() を呼び出している実装を見かけます。
これは一応正しく動きます。しかし、以下のようなクラスが追加されるとどうなるでしょう。
public class B extends A {
// cloneメソッドを定義しない
}
ここで、B のクローンを作成してみます。
B original = new B(); ... B copyObject = (B)original.clone();
B.clone() は定義されていない為、A.clone() が呼び出されます。
しかし、A.clone() は A のインスタンスを返すので
Bにキャストしようとした段階で ClassCastException が発生してしまいます。
A.clone() を以下のように実装していれば、この問題を防ぐことが出来ます。
public class A implements Cloneable {
public Object clone() throws CloneNotSupportedException {
A obj = (A)super.clone();
...
return obj;
}
}
一見、A.clone() は A のインスタンスを返すように思えるかもしれませんが
実際には B のインスタンスを返します。
B は A の派生クラスですので、clone() 内でキャスト例外も発生しませんし
Bにキャストする際にもうまくいきます。
このように、Object.clone() は実に有用なのです。
さらにこのメソッドは、全フィールドのShallow(浅い)コピーを実行します。
Shallow(浅い)コピーとは、オブジェクトの参照だけがコピーされる事を意味します。
public class A implements Cloneable {
public int v;
public StringBuffer buff;
}
このような場合、A.clone() を呼び出すと Object.clone() が呼び出されます。
このメソッドで、AのShallowコピーが実行されます。
A original = new A();
original.v = 10;
original.buff = new StringBuffer("abc");
A copyObject = (A)a.clone(); // originalのクローンを作成
copyObject.v = 20; // クローンの内容を変更
copyObject.buff.append("def"); // クローンの内容を変更
ここで、各オブジェクトの内容を出力してみます。
log(original.v); -> 10 log(original.buff.toString()); -> "abcdef" log(copyObject.v); -> 20 log(copyObject.buff.toString()); -> "abcdef"
このように、プリミティブ型(intなど)はオリジナルとコピーの実体が別なので
それぞれの値は異なりますが
Javaのオブジェクトは全て参照型なのでオリジナルとコピーは同じ実体を指します。
つまり、
original.v と copyObject.v は別物 original.buff と copyObject.buff は同一
ということになります。
まだこの段階で、A.clone() は定義されていない事を思い出してみて下さい。
オリジナルとコピーで buff の実体を異なるようにする為には
A.clone() メソッドを以下のように定義します。
public Object clone() throws CloneNotSupportedException {
A cloneObj = (A)super.clone();
cloneObj.buff = new StringBuffer(this.buff); // ここでbuffの複製を作成
// cloneObj.v = this.v は必要無い
return cloneObj;
}
こうすれば、buffのDeep(深い)コピーが実行されます。
vの明示的な複製は必要無いことに注意して下さい。
先程説明したように、super.clone() の呼び出しでvはShallowコピーされます。
プリミティブ型はShallowコピーで完全な複製が作成される為、
コーディング量を減らすという意味でも Object.clone() の呼び出しは有用です。
また、Immutableなインスタンスに対しても Shallowコピーで充分です。
「生成後、内容が変更される事がない」インスタンスのことを
Immutableなインスタンス(クラス)と言います。
基本クラスでは、String や Integer などは Immutable です。
反対に、StringBuffer は Mutable です。
Immutableインスタンスの内容を変更したい場合には、
新しくインスタンスを生成し直す必要があります。
ImmutableObject obj = new ImmutableObject("abc");
// objの内容は変更できないので、以下のようにインスタンスを再生成する
obj = new ImmutableObject("def");
これは一見とても効率が悪いように思えますが、
「一旦生成したら、その後内容が変更されないことが保証される」という意味で
非常に使える場面があります。
例えば、Immutableなインスタンスに対してはcloneメソッド内で
明示的にDeepコピーをする必要がありません。
A original = new A(); original.str = "abc"; A copyObject = (A)a.clone(); // originalのクローンを作成 // この段階で、オリジナルとコピーのstrは同じ実体を指す copyObject.str = "def"; // クローンの内容を変更 // この段階では、オリジナルとコピーは別の実体を指す log(original.str); -> "abc" log(copyObject.str); -> "def"
先程の例で、クローン時に毎回 StringBuffer の複製をしていた事を考えれば
こうった場合にはImmutableの方が効率が良いことがわかります。
Immutableの場合、内容を変更したい場合だけインスタンスを生成すれば良いのです。
他にも、Immutable なインスタンスはキャッシュするのにも適しています。
また、全ての例外クラスは Immutable として実装することが推奨されています。
CheckStyle : MutableException