class MyRect {
int x, y, w, h;
public MyRect (int x, y, w, h)
{ this.x = x; this.y = y; this.w = w; this.h = h; }
}
このクラスをコピーするメソッドを書いてみます。
名前はとりあえず myClone() としておきます。
class MyRect {
int x, y, w, h;
public MyRect (int x, y, w, h)
{ this.x = x; this.y = y; this.w = w; this.h = h; }
public MyRect myClone(){
return new MyRect(x, y, w, h);
}
}
まず、返り値は自分と同じ MyRect 型です。中で自分と同じ x, y, w, h を引数に
コンストラクタを呼んで、MyRect 型のインスタンス(コピー)を返しています。
継承を用いないときは、まさに必要十分な実装です。
class ColorRect extends MyRect {
int col;
public ColorRect(int x, int y, int w, int h, MyColor col){
super(x, y, w, h);
this.col = col;
}
}
ここで、もし ColorRect クラスが myClone() を実装しなかったら何が起こるでしょうか。
親クラス(スーパークラス・MyRect の myClone() は普通に呼び出すことが出来ます。
しかし、これが返すのはあくまで MyRect 型であって、ColorRect() 型ではありません。
ColorRect cr = ...; ... ColorRect cr2 = cr.myClone();とすると、最後の行でエラーになってしまいます。 clone() の挙動は「自分と同じものを返す」はずなので、これが中途半端に使えたり使えなかったりするのは とても危険です。
class MyRect implements Cloneable {
public MyRect clone(){
MyRect r;
try {
r = (MyRect)super.clone();
} catch (CloneNotSupportedException ce)
{ throw new RuntimeException();/* 適切なエラー処理 */}
r.set(x, y, w, h);
return r;
}
void set(int x, int y, int w, int h)
{ this.x = x; this.y = y; this.w = w; this.h = h; }
}
ポイントは以下です。
一番のポイントは super.clone()を呼んでいるところです。MyRect は見かけ何も継承していないようですが、実は Object 型を継承しています。なので、ここでは Object#clone() が呼ばれています。この Object#clone() は、自分自身と同じ型のインスタンスを作成し、インスタンスの変数を全部コピー (memcpy) してくれます。(詳しくは下で述べます)
次に、MyRect を継承した ColorRect の clone() を見てみます。
class ColorRect extends MyRect {
MyColor col;
...
public ColorRect clone(){
ColorRect r = (ColorRect)super.clone();
r.setColor(col.clone());
return r;
}
void setColor(MyColor col)
{ this.col = col; }
}
基本的に同じですが、先ほどとはいくつか違いがあります。
public class Object {
protected Object clone() throws CloneNotSupportedException {
...
}
}
メソッドの動作は、二つあります。
こちらも絵を書いてみました。
ColorRect のインスタンスから Object#clone () が呼ばれると、まず ColorRect 型の変数が作られ、
さらにすべての変数が memcpy されます。MyColor への参照が含まれていますが、
これも「字面通り」コピーされるので、単にアドレスがコピーされるだけです。
コピーされている様子を見るために、Object#clone() の実装を見てみましょう。
今回は厳密な java ではないのですが、文法的にはJava 互換の Dalvik のソースコードを見てみます。
呼び出し関係:
dalvik/libcore/luni-kernel/src/main/java/java/lang/Object.java
vm/native/java_lang_Object.c :: Dalvik_java_lang_Object_internalClone
vm/alloc/Alloc.c :: vm/alloc/Alloc.c
vm/alloc/Alloc.c
---
Object* dvmCloneObject(Object* obj){
Object* copy;
int size = dvmObjectSizeInHeap(obj);
copy = dvmMalloc(size, flags);
memcpy(copy, obj, size);
return copy;
}
オブジェクトのサイズ分だけ memcpy が行われていることが分かります。
public class Object {
protected Object clone() throws CloneNotSupportedException {...
//[dalvik/libcore/luni-kernel/src/main/java/java/lang/Object.java]
public class Object {
...
protected Object clone() throws CloneNotSupportedException {
if (!(this instanceof Cloneable)) {
throw new CloneNotSupportedException("Class doesn't implement Cloneable");
}
return internalClone((Cloneable) this);
}
private native Object internalClone(Cloneable o);
...
}
Cloneable のインスタンスで無い場合、いきなり "throw" で、例外が投げられて終了していまいます。
つまり、Object#clone() は実装されているにもかかわらず、そのままでは生殺し状態で使えないのです。
public interface Cloneable {
}
なんと空っぽです。関数は一つも定義されていません。
結局、Cloneable インターフェースは、「clone() を使う」という宣言をするためだけに存在しているわけです。
このような使い方を「マーカーインターフェース」と呼びます。
vm/alloc/Alloc.c
...
Object* dvmCloneObject(Object* obj)
{
Object* copy;
int size;
// サイズ取得
size = dvmObjectSizeInHeap(obj);
// malloc している
copy = dvmMalloc(size, flags);
// memcpy (コピー)
memcpy(copy, obj, size);
DVM_LOCK_INIT(©->lock);
return copy;
}