参照型変数(主に、自分で作成したクラスのオブジェクト)をコピーする場合、単純に「=」で代入するだけでは不十分な場合があります。
参照型変数の中身は参照先(オブジェクトのメモリ領域を示すポインタ)です。
「=」で代入するだけでは、参照先だけがコピーされて、参照しているものは同じという状態になるので、コピー先の変更がコピー元に影響してしまいますし、その逆にコピー元の変更がコピー先に影響してしまいます。
これを避けたい場合は、cloneメソッドを用いて中身を丸ごとコピー(新たにメモリ領域を確保し書き込み)する必要があります。
以下の記述を行うことで、cloneメソッドを使用することができるようになります。
・cloneしたいクラスでCloneableインターフェースを実装する
・Cloneableインターフェースのclone()メソッドをオーバーライドする
・clone()メソッド内で、super.clone()メソッドを使用する
(super=Objectクラス)
・super.clone()メソッドはObject型で返ってくるので自身の型でキャストを行う
・CloneNotSupportedExceptionが返ってくる可能性があるので例外処理する
以下、サンプルコードです。
参照先のみコピーした場合とcloneで中身をコピーした場合を比較しています。
参照先のみコピーした場合は、コピー後にコピー先を変更した際にコピー元が影響を受けていますが、cloneで中身をコピーした場合は影響を受けていません。
【サンプルコード】
・CloneableItem.java
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 |
// 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
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 |
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()); } } |
【実行結果】
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[参照先のみコピーした場合] コピー前のコピー元オブジェクト: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 |
いかがでしたでしょうか。
javaでオブジェクトの中身をコピーしようとした場合、言語仕様上の問題により意外と複雑な実装になってしまいます。
そのため、サンプルコードを参考に実装するのがお勧めです。
Webには他の方が書いてくださったサンプルコードが色々とあるので、用途に応じて他のサンプルコードも参考にしてみると良いでしょう。
今回の記事がお役に立てれば幸いです。
これからもjavaに関する記事を書いていこうと思います!
コメント