技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

java:オブジェクトの中身をコピーする方法(cloneメソッド実装)

参照型変数(主に、自分で作成したクラスのオブジェクト)をコピーする場合、単純に「=」で代入するだけでは不十分な場合があります。
参照型変数の中身は参照先(オブジェクトのメモリ領域を示すポインタ)です。
「=」で代入するだけでは、参照先だけがコピーされて、参照しているものは同じという状態になるので、コピー先の変更がコピー元に影響してしまいますし、その逆にコピー元の変更がコピー先に影響してしまいます。
 
これを避けたい場合は、cloneメソッドを用いて中身を丸ごとコピー(新たにメモリ領域を確保し書き込み)する必要があります。
以下の記述を行うことで、cloneメソッドを使用することができるようになります。
・cloneしたいクラスでCloneableインターフェースを実装する
・Cloneableインターフェースのclone()メソッドをオーバーライドする
・clone()メソッド内で、super.clone()メソッドを使用する
 (super=Objectクラス)
・super.clone()メソッドはObject型で返ってくるので自身の型でキャストを行う
・CloneNotSupportedExceptionが返ってくる可能性があるので例外処理する
 
以下、サンプルコードです。
参照先のみコピーした場合とcloneで中身をコピーした場合を比較しています。
参照先のみコピーした場合は、コピー後にコピー先を変更した際にコピー元が影響を受けていますが、cloneで中身をコピーした場合は影響を受けていません。
 
【サンプルコード】
・CloneableItem.java
// cloneしたいクラスはCloneableインターフェースを実装する必要がある
public class CloneableItem implements Cloneable {

  // イミュータブルではない参照型変数を含む場合はそれも一緒にcloneが必要
  // (今回は割愛)
  private int itemId;
  private String itemName;

  public int getItemId() {
    return itemId;
  }
  public void setItemId(int itemId) {
    this.itemId = itemId;
  }
  public String getItemName() {
    return itemName;
  }
  public void setItemName(String itemName) {
    this.itemName = itemName;
  }

  // Cloneableインターフェースのclone()メソッドをオーバーライド
  @Override
  public CloneableItem clone() {
    CloneableItem clonedItem = null;
    // CloneNotSupportedExceptionを返す可能性があるので例外処理が必要
    try {
      // Object型で返ってくるのでキャストが必要
      clonedItem = (CloneableItem)super.clone();
    } catch (CloneNotSupportedException e) {
      e.printStackTrace();
    }
    return clonedItem;
  }

}

・ItemCloneMain.java
public class ItemCloneMain {
  public static void main(String[] args){

    System.out.println("[参照先のみコピーした場合]");
    CloneableItem cloneableItem1 = new CloneableItem();
    cloneableItem1.setItemId(1);
    cloneableItem1.setItemName("sword");
    System.out.println
      ("コピー前のコピー元オブジェクト:ID=" +
       cloneableItem1.getItemId() +
       " NAME=" +
       cloneableItem1.getItemName());
    CloneableItem cloneableItem2 = cloneableItem1;
    System.out.println
      ("コピー後のコピー元オブジェクト:ID=" +
       cloneableItem1.getItemId() +
       " NAME=" +
       cloneableItem1.getItemName());
    System.out.println
      ("コピー後のコピー先オブジェクト:ID=" +
       cloneableItem2.getItemId() +
       " NAME=" +
       cloneableItem2.getItemName());
    cloneableItem2.setItemId(2);
    cloneableItem2.setItemName("shield");
    System.out.println
      ("編集後のコピー元オブジェクト :ID=" +
       cloneableItem1.getItemId() +
       " NAME=" +
       cloneableItem1.getItemName());
    System.out.println
      ("編集後のコピー先オブジェクト :ID=" +
       cloneableItem2.getItemId() +
       " NAME=" +
       cloneableItem2.getItemName());

    System.out.println("");
    System.out.println("[cloneで中身をコピーした場合]");
    CloneableItem cloneableItem3 = new CloneableItem();
    cloneableItem3.setItemId(3);
    cloneableItem3.setItemName("armor");
    System.out.println
      ("コピー前のコピー元オブジェクト:ID=" +
       cloneableItem3.getItemId() +
       " NAME=" +
       cloneableItem3.getItemName());
    CloneableItem cloneableItem4 = cloneableItem3.clone();
    System.out.println
      ("コピー後のコピー元オブジェクト:ID=" +
       cloneableItem3.getItemId() +
       " NAME=" +
       cloneableItem3.getItemName());
    System.out.println
      ("コピー後のコピー先オブジェクト:ID=" +
       cloneableItem4.getItemId() +
       " NAME=" +
       cloneableItem4.getItemName());
    cloneableItem4.setItemId(4);
    cloneableItem4.setItemName("helm");
    System.out.println
      ("編集後のコピー元オブジェクト :ID=" +
       cloneableItem3.getItemId() +
       " NAME=" +
       cloneableItem3.getItemName());
    System.out.println
      ("編集後のコピー先オブジェクト :ID=" +
       cloneableItem4.getItemId() +
       " NAME=" +
       cloneableItem4.getItemName());

  }
}
 
【実行結果】
[参照先のみコピーした場合]
コピー前のコピー元オブジェクト:ID=1 NAME=sword
コピー後のコピー元オブジェクト:ID=1 NAME=sword
コピー後のコピー先オブジェクト:ID=1 NAME=sword
編集後のコピー元オブジェクト :ID=2 NAME=shield
編集後のコピー先オブジェクト :ID=2 NAME=shield

[cloneで中身をコピーした場合]
コピー前のコピー元オブジェクト:ID=3 NAME=armor
コピー後のコピー元オブジェクト:ID=3 NAME=armor
コピー後のコピー先オブジェクト:ID=3 NAME=armor
編集後のコピー元オブジェクト :ID=3 NAME=armor
編集後のコピー先オブジェクト :ID=4 NAME=helm