この記事では、浅いコピー(ShallowCopy:シャローコピー)と、深いコピー(DeepCopy:ディープコピー)について解説していきます。
配列を復習するには以下の記事がおすすめです。
		
浅いコピー(ShallowCopy:シャローコピー)とは
シャローコピーは、”参照情報”のみをコピーする方法です。
そのため、コピー元とコピー先のオブジェクトはメモリー上の同じデータを参照しています。
シャローコピーになる例①(配列内がプリミティブ型の場合)
配列をコピーする際に=演算子を用いるとシャローコピーになります。
Java
//コピー元の配列を生成
int[] original = {10,20,30,40,50};
//違う配列へコピー。
int[] replica = original;
//3番目の要素を500に書き換える。
replica[2] = 500;
//original配列の中身を出力。
System.out.println("original配列の中身を出力");
Arrays.stream(original).forEach(i -> System.out.print(i +" "));
System.out.println(System.getProperty("line.separator"));
//replica配列の中身を出力。
System.out.println("replica配列の中身を出力");
Arrays.stream(replica).forEach(i -> System.out.print(i +" "));
実行結果
original配列の中身を出力
10 20 500 40 50 
replica配列の中身を出力
10 20 500 40 50 
上記のコードでは5行目で=演算子で配列を代入しています。
その後8行目で、replicaオブジェクト3番目の要素の値を500に書き換えています。
シャローコピーでは、コピー元とコピー先のオブジェクトはメモリー上の同じデータを参照するため、
replicaオブジェクトの値のみ書き換えたつもりが、originalオブジェクトにも反映されることになってしまいます。
シャローコピーになる例②(配列内がオブジェクト型の場合)
配列内にオブジェクトがある場合はArrays.copyOfで生成した場合でも、シャローコピーになってしまいます。(もちろん、=演算子でコピーしてもシャローコピーとなってしまいます。)
Java
//コピー元の配列を生成
Axis[] original = {new Axis(10,20), new Axis(30,40)};
//違う配列へコピー。
Axis[] replica = Arrays.copyOf(original, original.length);
//書き換え前の値を出力。
System.out.println("前:" + original[0].x);
System.out.println("前:" + replica[0].x);
//値を書き換える。
replica[0].x=555;
//書き換え後の値を出力。
System.out.println("後:" + original[0].x);
System.out.println("後:" + replica[0].x);
実行結果
前:10
前:10
後:555
後:555
上記のコードでは5行目でArrays.copyOfを使ってコピーしています。
その後12行目で、replicaオブジェクト3番目の要素の値を500に書き換えています。
コピー先のオブジェクト内の変数の値を変えると、コピー元の配列の変数の値も変わってしまいます。
深いコピー(DeepCopy:ディープコピー)とは
ディープコピーは、メモリ上のデータ(インスタンス)をコピーする方法です。
コピー元とコピー先のオブジェクトは、メモリ上の別々のデータを参照していることになります。
ディープコピーになる例①(配列内がプリミティブ型の場合)
プリミティブ型からなる配列をコピーする際にArrays.copyOfを用いるとディープコピーになります。
Java
//コピー元の配列を生成
int[] original = {10,20,30,40,50};
//違う配列へコピー。
int[] replica = Arrays.copyOf(original, original.length);
//3番目の要素を500に書き換える。
replica[2] = 500;
//original配列の中身を出力。
System.out.println("original配列の中身を出力");
Arrays.stream(original).forEach(i -> System.out.print(i +" "));
System.out.println(System.getProperty("line.separator"));
//replica配列の中身を出力。
System.out.println("replica配列の中身を出力");
Arrays.stream(replica).forEach(i -> System.out.print(i +" "));
System.out.println(System.getProperty("line.separator"));
実行結果
original配列の中身を出力
10 20 30 40 50 
replica配列の中身を出力
10 20 500 40 50 
上記のコードでは5行目でArrays.copyOfを使ってコピーしています。
その後8行目で、replicaオブジェクト3番目の要素の値を500に書き換えています。
ディープコピーでは、コピー元とコピー先のオブジェクトは、メモリ上の別々のデータを参照するため、
replicaオブジェクトに設定した値が、originalオブジェクトに影響することはありません。
ディープコピーになる例②(cloneメソッドを用いる)
配列内がプリミティブ型の場合は、cloneメソッドを使うことでディープコピーを実現できます。
Java
//コピー元の配列を生成
int[] original = {10,20,30,40,50};
//違う配列へコピー。
int[] replica = original.clone();
//3番目の要素を500に書き換える。
replica[2] = 500;
//original配列の中身を出力。
System.out.println("original配列の中身を出力");
Arrays.stream(original).forEach(i -> System.out.print(i +" "));
System.out.println(System.getProperty("line.separator"));
//replica配列の中身を出力。
System.out.println("replica配列の中身を出力");
Arrays.stream(replica).forEach(i -> System.out.print(i +" "));
System.out.println(System.getProperty("line.separator"));
実行結果
original配列の中身を出力
10 20 30 40 50 
replica配列の中身を出力
10 20 500 40 50 
ディープコピーになる例③(配列内がオブジェクト型の場合)
オブジェクトからなる配列をコピーする際は、コピー元の配列の変数を基にインタンスを生成して代入すると、ディープコピーになります。
Java
//コピー元の配列を生成
Axis[] original = {new Axis(10,20), new Axis(30,40)};
//違う配列へコピー。
Axis[] replica = new Axis[original.length];
//originalの値をreplicaへコピーする。
for(int i = 0; i < original.length; i++) {
    replica[i] = new Axis(original[i].x, original[i].y);
}
//書き換え前の値を出力。
System.out.println("前:" + original[0].x);
System.out.println("前:" + replica[0].x);
//値を書き換える。
replica[0].x=555;
//書き換え後の値を出力。
System.out.println("後:" + original[0].x);
System.out.println("後:" + replica[0].x);
実行結果
前:10
前:10
後:10
後:555
上記のコードでは5行目では、コピー元の配列と同じサイズでインスタンスを生成し、
8行目~10行目のIF文で、コピー元の配列の変数を基に新しいインスタンスを生成して、値の代入を行っています。
こうすることで17行目でコピー先の配列の変数の値を書き換えても、それがコピー元に影響することがなくなります。
ディープコピーになる例④(配列内がオブジェクト型の場合にcloneメソッドを使用)
まず、コピーしたいオブジェクトを、以下のようにCloneableインターフェースをimplementsし、cloneメソッドをオーバーライドします。
Axis.java
public class Axis implements Cloneable{
    int x;
    int y;
    //コンストラクタ
    public Axis(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }
    //ゲッターセッター
    public int getX() {return x;}
    public void setX(int x) {this.x = x;}
    public int getY() {return y;}
    public void setY(int y) {this.y = y;}
    //cloneメソッドを実装
    public Axis clone(){
        Axis result = new Axis(this.x, this.y);
        result.x = this.x;
        result.y = this.y;
        return result;
    }
}
以下のようにcloneメソッドを呼び出します。
Java
//コピー元の配列を生成
Axis[] original = {new Axis(10,20), new Axis(30,40)};
//違う配列へコピー。
Axis[] replica = {original[0].clone(), original[1].clone()};
//書き換え前の値を出力。
System.out.println("前:" + original[0].x);
System.out.println("前:" + replica[0].x);
//値を書き換える。
replica[0].x=555;
//書き換え後の値を出力。
System.out.println("後:" + original[0].x);
System.out.println("後:" + replica[0].x);
実行結果
前:10
前:10
後:10
後:555
コピーしたいオブジェクトで予め`clone`メソッドをオーバーライドしておくことで、コピーしたいタイミングで`clone`メソッドを呼び出すだけでディープコピー出来るようになります。(5行目)
ディープコピー応用編
もし、オブジェクト型のフィールドがいる場合は、そのオブジェクト型にもcloneメソッドをオーバーライドしたうえで、result.obj = this.obj.clone();と記述する必要があります。(25行目)
Java
public class Axis implements Cloneable{
    int x;
    int y;
    ObjectABC obj;
    //コンストラクタ
    public Axis(int x, int y, this.obj) {
        super();
        this.x = x;
        this.y = y;
                this.obj = obj;
    }
    //ゲッターセッター
    public int getX() {return x;}
    public void setX(int x) {this.x = x;}
    public int getY() {return y;}
    public void setY(int y) {this.y = y;}
    //cloneメソッドを実装
    public Axis clone(){
        Axis result = new Axis(this.x, this.y, this.obj);
        result.x = this.x;
        result.y = this.y;
        result.obj = this.obj.clone();
        return result;
    }
}

以上で記事の解説はお終い!
もっとJavaやSpringを勉強したい方にはUdemyがオススメ!同僚に差をつけよう!
 頭脳一式
				頭脳一式	



