※当サイトの記事には、広告・プロモーションが含まれます。

Javaの標準APIで配列のShallow CopyとDeep Copyを試してみる

www.itmedia.co.jp

amazing...

Shallow CopyDeep Copyの違い

Shallow CopyDeep Copyの違いについては、

stackoverflow.com

⇧ 上図がイメージしやすいかと。

一応、Wikipediaから抜粋すると、

Shallow copy

One method of copying an object is the shallow copy. In that case a new object B is created, and the fields values of A are copied over to B. This is also known as a field-by-field copy, field-for-field copy, or field copy. If the field value is a reference to an object (e.g., a memory address) it copies the reference, hence referring to the same object as A does, and if the field value is a primitive type it copies the value of the primitive type. In languages without primitive types (where everything is an object), all fields of the copy B are references to the same objects as the fields of original A. The referenced objects are thus shared, so if one of these objects is modified (from A or B), the change is visible in the other. Shallow copies are simple and typically cheap, as they can usually be implemented by simply copying the bits exactly.

https://en.wikipedia.org/wiki/Object_copying

Deep copy

An alternative is a deep copy, meaning that fields are dereferenced: rather than references to objects being copied, new copy objects are created for any referenced objects, and references to these placed in B. The result is different from the result a shallow copy gives in that the objects referenced by the copy B are distinct from those referenced by A, and independent. Deep copies are more expensive, due to needing to create additional objects, and can be substantially more complicated, due to references possibly forming a complicated graph.

Deep copy is a process in which the copying process occurs recursively. It means first constructing a new collection object and then recursively populating it with copies of the child objects found in the original. In case of deep copy, a copy of object is copied in other object. It means that any changes made to a copy of object do not reflect in the original object. In python, this is implemented using “deep copy()” function.

https://en.wikipedia.org/wiki/Object_copying

⇧ 違いは、オブジェクトに対する変更が共有されるかどうかってことですかね。

Javaの標準APIで配列のShallow CopyDeep Copy

ネットの情報が、侃々諤々としていて、

stackoverflow.com

stackoverflow.com

eng-entrance.com

www.zunouissiki.com

⇧ 情報が錯綜し過ぎなんだけど、Wikipediaの「Shallow Copy」と「Deep Copy」の説明を正しいと仮定して確認するしかないですかね。

java.lang.Object.clone()

package lesson.chapter04;

import java.util.Arrays;

public class Lesson4_10 {

	public static void main(String[] args) {
		// 1次元のコピー
		System.out.println("【1次元のコピー】");
		int[] original = {1, 2, 3, 4, 5};
		int[] replica = original.clone();
		replica[2] = 777;

		Arrays.stream(original).forEach(i -> System.out.print(i +" "));
		System.out.println();
		
		Arrays.stream(replica).forEach(i -> System.out.print(i +" "));
		System.out.println();

		// 2次元のコピー
		System.out.println("【2次元のコピー】");
		int[][] arrayA = {{1, 2}, {1, 2}, {1, 2, 3}};
		int[][] arrayB = arrayA.clone();
		
		for (int[] tmp: arrayA) {
			for (int val: tmp) {
				System.out.print(val + " ");
			}
		}
		
		for (int i = 0; i < arrayB.length; i++) {
			for (int j = 0; j < arrayB[i].length; j++) {
				arrayB[i][j] = 777;
			}
		}

		System.out.println();
		for (int[] tmp: arrayA) {
			for (int val: tmp) {
				System.out.print(val + " ");
			}
		}
		System.out.println();
	}

}
    

⇧ の結果は、

1次元の配列は、Deep Copyできてるっぽいけど、2次元の配列は、Shallow Copyっぽい感じになってる模様。

 

java.lang.System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

package lesson.chapter04;

public class Lesson4_11 {

	public static void main(String[] args) {
		// 1次元
		System.out.println("【1次元のコピー】");
		char[] arrayA = {'A', 'B', 'C', 'D', 'E'};
		char[] arrayB = new char[arrayA.length];
		System.arraycopy(arrayA, 0, arrayB, 0, arrayA.length);
		
		for (int i = 0; i < arrayB.length; i++) {
			arrayB[i] = 'A';
		}
		for (char c: arrayA) {
			System.out.print(c);
		}
		System.out.println();
		for (char c: arrayB) {
			System.out.print(c);
		}
		System.out.println();

		// 2次元
		System.out.println("【2次元のコピー】");
		char[][] array2A = {{'A', 'B', 'C', 'D', 'E'}, {'A', 'B', 'C', 'D', 'E'}};
		char[][] array2B = new char[array2A.length][];
		for (int i = 0; i < array2A.length; i++) {
			array2B[i] = new char[array2A[i].length];
			System.arraycopy(array2A[i], 0, array2B[i], 0, array2A[i].length);
		}
		
		for (int i = 0; i < array2B.length; i++) {
			for (int j = 0; j < array2B[i].length; j++) {
				array2B[i][j] = 'Z';
			}
		}
		
		for (int i = 0; i < array2B.length; i++) {
			for (int j = 0; j < array2B[i].length; j++) {
				System.out.print(array2B[i][j]);
			}
			System.out.println();
		}

		for (int i = 0; i < array2A.length; i++) {
			for (int j = 0; j < array2A[i].length; j++) {
				System.out.print(array2A[i][j]);
			}
			System.out.println();
		}
	}

}
    

⇧ の結果は、

1次元の配列、2次元の配列のどちらもDeep Copyになってそうだけど。

今回はプリミティブ型の配列での結果だけど、参照型の配列だと違った結果になるんかな?

www.ne.jp

Object#clone()のコピー方法は、シャローコピーshallow copy:浅いコピー)と呼ばれるもの。
これに対するのがディープコピーdeep copy:深いコピー)。

Javaクローンメモ(Hishidama's Java Cloneable Memo)

シャローコピーは、オブジェクトのフィールド(メンバー変数)がオブジェクト(参照型)である場合に、その参照をコピーするだけ。
つまりフィールドのオブジェクトは、複製元と複製先で同じオブジェクトを指すことになる。

Javaクローンメモ(Hishidama's Java Cloneable Memo)

これに対しディープコピーは、「フィールドのオブジェクト自身も複写する方式」を指す。
ディープコピーを自動的に行うメソッドは用意されていないので、オブジェクトのディープコピーを作りたいのであれば、ディープコピー用のメソッドを自分で作る必要がある。

Javaクローンメモ(Hishidama's Java Cloneable Memo)

⇧ 上記サイト様の説明を見ると、やはり参照型の配列で試す必要があった気がする。

2022年10月11日(火)追記:↓ ここから

System.arrayCopy()について、参照型の配列は、Shallow Copyになったっぽいです。

■/test-silver/src/lesson/chapter10/Sample.java

package lesson.chapter10;

public class Sample {
	int num;
	Sample(int num) {
		this.num = num;
	}
}    

■/test-silver/src/lesson/chapter10/Lesson10_64.java

package lesson.chapter10;

public class Lesson10_64 {

	public static void main(String[] args) {
		Sample[] array = {
				new Sample(10),
				new Sample(20)
		};
		Sample[] array2 = new Sample[2];
		System.arraycopy(array, 0, array2, 0, array.length);
		array2[1].num = 10;
		System.out.println(array[1].num);
	}

}

⇧ で実行すると、

というように配列のインスタンスが共有されている...

配列の要素がプリミティブ型か参照型かで、System.arrayCopy()の挙動が変わるってことみたいね。

2022年10月11日(火)追記:↑ ここまで

毎度モヤモヤ感が半端ない...

今回はこのへんで。