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