Object#clone()でクローンできるよ?
Javaでは標準でクローンをサポートしていますので、簡単にクローンができます。
・・・が、Objectクラスにあるクローンメソッドは、シャローコピー(浅いコピー)によるクローンです。
ざっくり言うと、変数の箱は用意するけど、中身は共有することになります。
よって、クローン元のオブジェクトに操作を行うと、クローン先のオブジェクトにも反映される等、影響を受ける可能性があります。
その時点でのオブジェクトをバックアップしたりするには、不向きです。(・・というか、困ります)
これってクローンじゃないじゃん?
いいえ。立派なクローンです。
Javaのオブジェクトは、参照型というものでできます。
つまり、実体のデータを指し示す参照(≒ポインタ)をコピーしたことになります。
ちなみに、クローン羊を作るようにオブジェクトの実体をコピーすることを「ディープコピー」といいます。
じゃあ、実体をクローンしたいんだけど・・・?
はい、clone()メソッドをオーバーライドしたり、その他メソッドでひたすらに変数をコピーしてください。
しかも、上の参照コピーを考慮してやると良いです。
なんて言うと、怒られるので・・(-_-メ)
汎用サンプルを用意してみました。
サンプル
クローン対象のCloneTestクラスは、色から連想する果物をHashMapで格納しています。
これをクローンした後、クローン元を操作した際の結果を出力します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.HashMap; import java.util.Map; public class CloneTest implements Cloneable,Serializable { private HashMap<String, String> map = null; public CloneTest() { map = new HashMap<String, String>(); map.put("red", "りんご"); map.put("green", "メロン"); map.put("yellow", "バナナ"); } public Object cloneDeep() throws Exception { byte[] byteArray = null; //クローン元のオブジェクトをバイト配列へ try(ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos)) { oos.writeObject(this); byteArray = baos.toByteArray(); } //バイト配列からオブジェクトを再構成 Object retObject = null; try(ByteArrayInputStream bais = new ByteArrayInputStream(byteArray); ObjectInputStream ois = new ObjectInputStream(bais)) { retObject = ois.readObject(); } return retObject; } @Override public String toString() { StringBuilder sb = new StringBuilder(); for(Map.Entry<String, String> entry : map.entrySet()) { sb.append(entry.getKey() + ":" + entry.getValue() + System.lineSeparator()); } return sb.toString(); } public static void main(String[] args) { try { // //0.初期状態をそのまま出力 // CloneTest test0 = new CloneTest(); System.out.println("===== 0.初期状態 ====="); System.out.println(test0.toString()); // //1.Object#clone()でクローン作成→クローン元を加工 // CloneTest test1 = new CloneTest(); CloneTest test1clone = (CloneTest)test1.clone(); test1.map.replace("green", "青りんご"); System.out.println("===== 1.Object#clone()でクローン作成 ====="); System.out.println("-- 1-1.クローン元 --"); System.out.println(test1.toString()); System.out.println("-- 1-1.クローン先 --"); System.out.println(test1clone.toString()); // //2.CloneTest#cloneDeep()でクローン作成→クローン元を加工 // CloneTest test2 = new CloneTest(); CloneTest test2clone = (CloneTest)test2.cloneDeep(); test2.map.replace("green", "青りんご"); System.out.println("===== 2.CloneTest#cloneDeep()でクローン作成 ====="); System.out.println("-- 2-1.クローン元 --"); System.out.println(test2.toString()); System.out.println("-- 2-1.クローン先 --"); System.out.println(test2clone.toString()); }catch(Exception e) { e.printStackTrace(); } } } |
実行結果
一目瞭然ですが、Object#clone()によるシャローコピーと、CloneTest#cloneDeep()によるディープコピーの挙動の差がわかります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | ===== 0.初期状態 ===== red:りんご green:メロン yellow:バナナ ===== 1.Object#clone()でクローン作成 ===== -- 1-1.クローン元 -- red:りんご green:青りんご yellow:バナナ -- 1-1.クローン先 -- red:りんご green:青りんご yellow:バナナ ===== 2.CloneTest#cloneDeep()でクローン作成 ===== -- 2-1.クローン元 -- red:りんご green:青りんご yellow:バナナ -- 2-1.クローン先 -- red:りんご green:メロン yellow:バナナ |
サンプルの解説
一度、シリアライズ化した後、オブジェクトを再構成することで、簡単にディープコピーができます。
ファイルにセーブして、ファイルからロードしたようなイメージです。
このサンプルは、シリアライズ化を利用することで汎用的にディープコピーできるようにしています。
よって、クローンしたいオブジェクトは、「Serializable」を実装する必要があります。
というわけで、ちょっと条件ありなので、常に使えるわけではないです。。