minimize

オブジェクトのクローン(複製)を可能にしたい場合には、そのクラスに
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() も呼び出されません。

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コピー

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とは

「生成後、内容が変更される事がない」インスタンスのことを
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

[コメント(0)]