Categories: Java

【Java入門】【配列編】シャローコピー(Shallow Copy)とディープコピー(Deep Copy)の違い

この記事では、浅いコピー(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がオススメ!同僚に差をつけよう!

issiki_wp