Java の clone() メソッドについて

プログラムを書いてて、オブジェクトをコピーしたくなること、あると思います。 こういう時のために、Java には clone() というメソッドが用意されています。
…なんですが、継承とかインターフェースとか色々考えすぎた結果、初心者にはけっこうとっつきにくい ものになっています。ここでは、Java の clone() の正しい書き方と、なぜこんな仕様になってしまったのかについて 書いてみます。

clone() の意味

clone メソッドでやりたいことは、「オブジェクトのコピーを作る」ことです。 でもコピーって何をしたらいいんでしょうか? これらを、設計者の意図通りに、自由に定義できるのが clone() メソッドです。

お手軽 clone()

例えばお絵かきソフトを作ってて、長方形クラスを作ったとします。
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 型のインスタンス(コピー)を返しています。 継承を用いないときは、まさに必要十分な実装です。

myClone() だと、どんなときに困るか

ですが、継承を使おうとすると、myClone() はちょっと困ったことが起きます。 例えば、MyRect (長方形)クラスを拡張して、色がついた ColorRect クラスを作ったとします。
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() の挙動は「自分と同じものを返す」はずなので、これが中途半端に使えたり使えなかったりするのは とても危険です。
また、場合によってはコンストラクタと clone の処理が違う場合もあるかもしれません。 そのような場合は、スーパークラスの clone() を呼び出す方が自然な処理が出来ます。 このような問題を解決するために、Java の clone は設計されました。

正しい clone() の書き方

それでは、正しい作法に乗っ取った 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; }
}
基本的に同じですが、先ほどとはいくつか違いがあります。 やはり一番のポイントは、ここでも super.clone() を呼んでいることです。コンストラクタを呼んではいけません。

生まれるいくつかの疑問

ここまでで、ひとまず clone の使い方の説明は終わりです。 でもこれだけでは、色々と不自然に感じられます。 このあたりの疑問に答えるには、Java でのオブジェクトのしくみと、Object#clone() の中身を見る必要があります。

Java のオブジェクト

一般に、オブジェクトのインスタンスは型 + インスタンス変数で出来ています。 オブジェクトのインスタンスがいくつあっても、関数の定義とか、スタティック変数は 1 つで十分です。 これはクラスの定義のところに保持されます。これに対し、メンバ変数はインスタンスごとに変わるので、 新たにオブジェクトのインスタンス(new MyRect() のように)が作られた時に確保されます。
上の図で、「MyRect のクラス定義」と書かれたところがクラス定義です。static 変数や関数の定義はここに入っています。一つ大事なのが、親クラスの定義へのポインタ(ここでは super と書いた) です。MyRect クラスの場合は、Object クラスを指しています。
これらに対し、メンバ変数は上の横長の部分に格納されています。ここでは MyRect のインスタンス、r の領域が確保されています。ここには、まず自分が何の型かという情報が含まれています。(ここでは点線の矢印が MyRect のクラス定義に向かって生えています) その後ろに、x, y, w, h といったインスタンス変数の情報が含まれています。

次に、MyRect を継承した ColorRect の例を示します。こちらもほぼ同じですが、MyColor 型のオブジェクトへの参照が含まれているのが少し違います。メンバ変数にオブジェクトが含まれる場合は、これは直接同じ場所におかれるのではなくて、ポインタとして保持されます。(要はCの構造体のポインタ参照とと同じです)

Object#clone

Java の clone() 最大のポイントは、Object クラスの clone() メソッドと、Cloneable インターフェースです。 まずは、Java の clone() メソッドを見てみます。 このメソッドの定義は以下です。
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 が行われていることが分かります。

Object#clone() の不思議

しかし、この Object#clone() 、少し不思議な定義になっています。 定義をもう一度載せてみます。
public class Object {
  protected Object clone() throws CloneNotSupportedException {...
この理由を探るために、Object クラスのソースを見てみます。
//[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() は実装されているにもかかわらず、そのままでは生殺し状態で使えないのです。
clone() を使うには、 する必要があると分かりました。では、Cloneable インターフェースにはどんなメソッドが含まれているのでしょうか?
Clonebale インターフェースを見てみます。
public interface Cloneable {
}
なんと空っぽです。関数は一つも定義されていません。
結局、Cloneable インターフェースは、「clone() を使う」という宣言をするためだけに存在しているわけです。 このような使い方を「マーカーインターフェース」と呼びます。

ついでに、Object#clone() の実装を見てみる (ただし Dalvik)

ついでに、Obkject#clone() を見てみます。要は、オブジェクトのサイズ分だけ malloc し、memcpy しているだけです。簡単ですね。
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;
}

なぜこんなことになっているのか

おそらく、Java の clone() を設計した人は、本来ならこうしたかったのだと思います。 Cloneable を継承しないと、そもそも clone() が存在しないし、protected や変な例外も起こらないし、 とても素直です。
しかし、Java では 多重継承がサポートされていないため、Cloneable をクラスにしてしまうと、とても不便です。 ほかのクラスを継承しつつ clone() を使うことが不可能になってしまいます。 このため、Cloneable はインターフェースにする必要があったのですが、それだと今度は clone() の実装を 書くことが出来ません。また、clone() の操作はオブジェクト管理の根幹に関わることなので、むしろ Object クラスの中に書くべき…という意図もあるのかもしれません。

まとめ

Java の clone() の体系は、一見不可解に見えますが、背景を知るととても興味深いです。 ただ、文法的なきれいさを求めたため、かえって知らない人の混乱を招いている気もします。 このストイックなのかごちゃ混ぜなのか分からない感じが Java っぽくて好きです。 (でも、例えば Python だともっととっつきやすいですけど…)