Object#cloneを学ぶ はじめに 書籍Effective Java プログラミング言語ガイドの"項目10 cloneを注意してオーバーライドする"を 読んでみたが、cloneメソッドについて理解できなかったので実装しながらcloneメソッドを理解する。 まず、Web検索した情報で簡単なものを読んで、疑問に思ったことは、 クローンつまりオブジェクトの複製を作るには、Object#cloneを実装するだけでは駄目なようです。 java.lang.Cloneableインタフェースを実装しなければならないようなのでソースコードを書いてみます。 [ java]$ cat TestClone.java public class TestClone { private int value; public TestClone(int value) { this.value = value; } protected Object clone() throws CloneNotSupportedException { return new TestClone(value); } } [ java]$ javac TestClone.java とりあえず、上記のソースコードをコンパイルしてみました。問題なくコンパイルできました。 コンパイル出来たのでTestCloneクラスを呼び出すテスト用クラスを実装します。 [ java]$ cat TestCloneRunner.java public class TestCloneRunner { public static void main(String [] args) { TestClone original = new TestClone(1); // 複製を作ってみる TestClone cloneObject = (TestClone)original.clone(); } } [ java]$ javac TestCloneRunner.java TestCloneRunner.java:6: 例外 java.lang.CloneNotSupportedException は報告されませ ん。スローするにはキャッチまたは、スロー宣言をしなければなりません。 TestClone cloneObject = (TestClone)original.clone(); ^ エラー 1 個 テスト用クラスのTestCloneRunnerクラスを実装してみました。CloneNotSupportedExceptionを 実装しろとコンパイラが怒っているので修正します。 [ java]$ cat TestCloneRunner.java public class TestCloneRunner { public static void main(String [] args) { TestClone original = new TestClone(1); // 複製を作ってみる try { TestClone cloneObject = (TestClone)original.clone(); } catch (CloneNotSupportedException cloneNotEx) { cloneNotEx.printStackTrace(); } } } [ java]$ javac TestCloneRunner.java 今度は問題なくコンパイルに成功したので、実行してみます。 [ java]$ java TestCloneRunner 正常終了してしまいました。予定ではjava.lang.Cloneableインタフェースを実装(implements)していないので CloneNotSupportedExceptionが発生してエラーになる予定でした。 JavaTM 2 Platform, Standard Edition, 1.4.0 API 仕様のドキュメントを確認してみます。(いわゆるJavaDocです) 先ほどコーディングしたTestCloneクラスは、以下の用件を満たしているようです。 (抜粋) このオブジェクトのコピーを作成して返します。「コピー」の正確な意味合いは、オブジェクトのクラスによって異なります。一般的には、任意のオブジェクト x について、次の式 x.clone() != x が true であり、次の式 x.clone().getClass() == x.getClass() も true であることですが、これらも絶対的な要件ではありません。また次の式 x.clone().equals(x) も通常 true になりますが、これも絶対的な要件ではありません。 通常、super.clone を呼び出すことで返されるオブジェクトを取得できます。クラスおよびそのスーパークラスすべて (Object を除く) がこの規則に従う場合、x.clone().getClass() == x.getClass() が成立します。 java.lang.Cloneableとjava.lang.ClassNotSupportedExceptionのJavaDocも確認してみました。 私の書いたソースコードでも問題なさそうなんです。分からないので直接JDKのJavaのソース内でclone()メソッドが どのように書かれているのかを参考にしてみたいと思います。 java.util.Dateクラス,java.util.Calenderクラス,java.util.ArrayListクラスのソースコードのcloneメソッドを読んでみると super.clone()を呼び出していました。ここでひとつ疑問です。 ・super.clone()メソッドを必須でよびださなければならないのか? 上記JavaDocから"通常、super.clone を呼び出すことで返されるオブジェクトを取得できます。"と書かれているので 必須とは書いていないがセオリーとしてsuper.cloneを呼び出すのでしょうか?調べてみる必要がありそうです。 とりあえず、super.clone()メソッドで書き直してみます。JavaDocの文章からObject#cloneメソッドを使えば シャローコピー(sharrow copy)が可能のようです。仮に私が今まで書いたソースコードだとフィールドが増えるごとに コンストラクタ等でコードをコピーする必要があります。具体的には以下のような感じです。 ■いままでのコード public class TestClone { private int value; public TestClone(int value) { this.value = value; } protected Object clone() throws CloneNotSupportedException { return new TestClone(value); } } ■仮にフィールドを増やしたとすると、コンストラクタでコピーする分も増やさなければなりません。 public class TestClone { private int value; private long ago; // フィールドの追加 // プリミティブフィールドが増えると、それようのそのためのコンストラクタも膨大になってしまう。 public TestClone(int value) { this.value = value; } // フィールド追加に対するコンストラクタも追加する。 public TestClone(int value, long ago) { this.value = value; this.ago = ago; } protected Object clone() throws CloneNotSupportedException { return new TestClone(value); } } 実際の開発では、フィールド数が1つということは無く、相当数になります。 それをコピーしてくれるのがsuper.clone()メソッドつまりObject#clone()メソッドでは無いのでしょうか? 実際にソースコードを書いて確認してみます。 ■super.clone()を使ったソースコードに書き直す [ java]$ cat TestClone.java public class TestClone { private int value; public TestClone(int value) { this.value = value; } protected Object clone() throws CloneNotSupportedException { return super.clone(); } } [ java]$ javac TestClone.java 問題なくコンパイルできました。実際にテストしてみます。 [ java]$ java TestCloneRunner java.lang.CloneNotSupportedException: TestClone at java.lang.Object.clone(Native Method) at TestClone.clone(TestClone.java:10) at TestCloneRunner.main(TestCloneRunner.java:7) はじめの予想通りCloneableインタフェースを実装していないのでObject#clone(Native Method)を呼び出したところで 例外が発生しました。これを回避するためにClonableインタフェースを実装します。 [ java]$ cat TestClone.java public class TestClone implements Cloneable { private int value; public TestClone(int value) { this.value = value; } protected Object clone() throws CloneNotSupportedException { return super.clone(); } } [ java]$ javac TestClone.java 今度は、正常に動作すると予想できます。 [ java]$ java TestCloneRunner 問題なく動作しました。今度はクローン(複製)されたオブジェクトのフィールド(private int value)に 同じ値が入っているか確認してみます。これで元のオブジェクトと同じ値であればsuper.clone()が使われている理由が わかります。つまり自分でフィールド数分のコンストラクタ定義をする必要がなくなります。 取り合えず、getValue()メソッドを実装します。 [ java]$ cat TestClone.java public class TestClone implements Cloneable { private int value; public TestClone(int value) { this.value = value; } protected Object clone() throws CloneNotSupportedException { return super.clone(); } public int getValue() { return value; } } [ java]$ javac TestClone.java [ java]$ cat TestCloneRunner.java public class TestCloneRunner { public static void main(String [] args) { TestClone original = new TestClone(1); // 複製を作ってみる try { TestClone cloneObject = (TestClone)original.clone(); System.out.println(cloneObject.getValue()); } catch (CloneNotSupportedException cloneNotEx) { cloneNotEx.printStackTrace(); } } } [ java]$ javac TestCloneRunner.java [ java]$ java TestCloneRunner 1 見事結果で1を出力しました! ■最後に この記事を書く前よりは、Object#cloneメソッドについて知識がついたので、Effective Javaに 再度戦いを挑もうと思います。またここには記述していませんが、Object#cloneを調べていくと 自然とシャローコピー、ディープコピーの意味合いも分かるようになると思います。