検索

キーワード


【Java】配列のコピーの使い方を解説! Shallowコピーと Deepコピーの違いとは?

  • 公開日:2020-10-02 09:04:30
  • 最終更新日:2021-01-25 17:51:57
【Java】配列のコピーの使い方を解説! Shallowコピーと Deepコピーの違いとは?

こんにちは。

駆け出しプログラマーの松倉です!

ここでは、配列のコピーついて紹介・解説します。

プログラミング未経験の方、Javaについて勉強している方、これから勉強したいと思っている方の参考になれば幸いです! 

関連記事リンク:【Java】たくさんのデータをまとめよう! 配列の基本と使い方を解説!、【Java】配列と ArrayListの関係と違いについて解説! 配列同士のの比較方法とは?【Java】深いコピーと浅いコピー?『インスタンスのコピー』を詳しく解説!


配列のコピー

配列の要素をデータ処理する場合、元のデータを残してデータを並べ替えたり、あるグループのデータだけを取り出す場合があります。

配列は最初に要素数を決めてしまうため、あとから値の追加を行うことができません

そのため値を追加するための配列を別に用意して、元の配列の値をコピーする方法があります。

配列に含まれる要素の中で指定した範囲の要素をコピーして、新しい配列として取得することができます。

配列のコピーには ShallowコピーDeepコピーの 2種類があり、その性質を理解しないと間違ったコピーをしてしまいます。


この記事では配列のコピーについて以下のことを紹介・解説を行います

  • Shallowコピー
  • Deepコピー
  • cloneメソッド
  • arraycopyメソッド
  • ArrayList 


Shallowコピー

Shallowコピー」とは実体のコピーを行わず、オブジェクトをコピーする方式です。

配列は参照型のデータのため、基本型のように代入を行っても参照がコピーされるだけです。

下の図のように Shallowコピーではオブジェクトをコピーした場合は新たなオブジェクトは作成されますが、それはコピー元のオブジェクトと同じ実体を指しています。

そのためどちらかのオブジェクトを変更したら、もう片方のオブジェクトも変更されます。

これは代入演算子「=」の利用をしているのと同じになります。

こういったコピーのことを「Shallowコピー(浅いコピー)」と呼びます。

配列を独立した配列としてコピーする場合、新たな配列を作りそこにデータをコピーする必要があります。

        Shallowコピーのイメージ


Shallowコピーの例文を見てみましょう。

int[] original = {1, 10, 100};
int[] replica = original;

例文では配列 replicaに配列 originalをコピーしているように思えますが、これは参照先のアドレス(実体が保存されている場所)をコピーしているだけです。

したがって配列 originalと配列 replicaは同じ配列を指しています。

そのためどちらかの配列の要素の値が変更されると片方の値も変更されます。


Shallowコピーのサンプルコードを確認しましょう。

import java.util.Arrays;

public class Sample1 {
	public static void main(String[] args) {
		int[] original = { 1, 10, 100 };
		int[] replica = original;

		replica[0] = 0;

		System.out.println("---代入後--- ");
		System.out.println("コピー元 : " + Arrays.toString(original));
		System.out.println("コピー先 : " + Arrays.toString(replica));
	}
}


実行結果:

---代入後---
コピー元 : [0, 10, 100]
コピー先 : [0, 10, 100]

Shallowコピーの場合、コピー元の値を変更した時にコピー先の値も変更されます。



Deepコピー

Deepコピー」とは実体も含めてオブジェクトをコピーする方式です。

Shallowコピーに対して、参照先の実体も含め全てコピーして新たに配列に格納する方法を、「Deepコピー(深いコピー)」と呼びます。

Deepコピーは、各要素をコピーする手順を踏まなければなりません。

下の図のように Deepコピーでは、オブジェクトと参照する実体をコピーします。

そのため Deepコピーで作られたオブジェクトはコピー元とコピー先で別々の実体を参照します。

したがってどちらかのオブジェクトを変更したとしても、これらは別々のオブジェクトのためもう片方のオブジェクトには影響がありません

       Deepコピーのイメージ

Deepコピーの例として for文を使った配列のコピー方法があります。

for文の繰り返し処理を使用することで、コピー元の配列の要素を最初から順にコピー先に代入することができます。

for文を使った配列のコピーの理解は簡単にできますが、ほかの方法に比べてコードの量が多くなります。


for文の繰り返し処理を使ったサンプルコードを確認しましょう。

import java.util.Arrays;

public class Sample2 {
	public static void main(String[] args) {
		int[] original = { 10, 20, 30 };
		int[] replica = new int[original.length];

		for (int index = 0; index < original.length; index++) {
			replica[index] = original[index];
		}
		for (int index = 0; index < original.length; index++) {
			System.out.println("original[" + index + "] = " + replica[index]);
		}
		original[0] = 11;
		original[1] = 22;
		original[2] = 33;
		System.out.println("---代入後--- ");
		System.out.println("コピー元 : " + Arrays.toString(original));
		System.out.println("コピー先 : " + Arrays.toString(replica));
	}
}


実行結果:

original[0] = 10
original[1] = 20
original[2] = 30
---代入後--- 
コピー元 : [11, 22, 33]
コピー先 : [10, 20, 30]

Sample2では配列 replicaを作った後に新たに配列 originalを作り、for文の繰り返し処理を使って元の配列の値を 3個コピーしています。

Deepコピーではオブジェクトと参照する実体がコピーされるため、後から配列 replicaの値を代入しても配列 originalの値は変更されていません。


cloneメソッド

cloneメソッド」はある配列を別の配列へコピーすることができます。

cloneメソッドはオブジェクトのコピーを行うメソッドで、覚えるのが簡単かつコード量が少ないのが特徴です。

配列の変数の後に「.clone()」と加えることで、配列をコピーすることができます。


cloneメソッドはコピーする配列の要素がプリミティブ型のときは Deepコピーオブジェクト型のとき Shallowコピーになるので注意が必要です。


配列の要素がプリミティブ型の場合の cloneメソッドを使ったサンプルコードを確認しましょう。

import java.util.Arrays;

public class Sample3_1 {
	public static void main(String[] args) {
		int[] original = { 10, 20, 30 };
		int[] replica = original.clone();

		replica[0] = 100;
		System.out.println("---代入後--- ");
		System.out.println("コピー元 : " + Arrays.toString(original));
		System.out.println("コピー先 : " + Arrays.toString(replica));
	}
}


実行結果:

---代入後--- 
コピー元 : [10, 20, 30]
コピー先 : [100, 20, 30]

配列の要素がプリミティブ型のため Deepコピーになりました。

配列 replicaの値の代入を行っても配列 originalの値には影響が出ていないことが確認できました。


配列の要素が String型の場合の cloneメソッドを使ったサンプルコードを確認しましょう。

import java.util.Arrays;

public class Sample3_2 {
	public static void main(String[] args) {
		String[] original = { "A", "B", "C" };
		String[] replica = original.clone();

		replica[0] = "Z";
		System.out.println("---代入後--- ");
		System.out.println("コピー元 : " + Arrays.toString(original));
		System.out.println("コピー先 : " + Arrays.toString(replica));
	}
}


実行結果:

---代入後--- 
コピー元 : [A, B, C]
コピー先 : [Z, B, C]

プリミティブ型と同様に配列 originalの値の代入を行っても配列 replicaの値には影響が出ていないことが確認できました。

この結果を見ると、String型の配列要素も Deepコピーされているように見えますが、String型もオブジェクト型に含まれるため、実際には Shallowコピーされています。

Sample3_2では配列要素の String型オブジェクトそのもの(実体)に変更を加えているのではなく、新しい String型オブジェクト(実体)を作って配列の要素に代入しています。

そのためコピー元の配列には影響しないという事になります。


配列の要素が オブジェクト型の場合の cloneメソッドを使ったサンプルコードを確認しましょう。

public class Sample3_3 {
	public static void main(String[] args) {
		ObjArray[] original = {
				new ObjArray(5),
				new ObjArray(10)
		};
		ObjArray[] replica = original.clone();
		System.out.println("---代入前---");
		System.out.println("コピー元の要素[1] :" + original[1].num);
		System.out.println("コピー先の要素[1] :" + replica[1].num);

		if (original == replica) {
			System.out.println("同じインスタンスのため Shallowコピー");
		} else
			System.out.println("異なるインスタンスのため Deepコピー");

		replica[1].num = 50;
		System.out.println("\n---代入後---");
		System.out.println("コピー元の要素[1] :" + original[1].num);
		System.out.println("コピー先の要素[1] :" + replica[1].num);
		if (replica[1].equals(original[1])) {
			System.out.println("同じオブジェクトのため Shallowコピー");
		} else
			System.out.println("異なるオブジェクトのため Deepコピー");
	}
}

class ObjArray {
	int num;

	ObjArray(int num) {
		this.num = num;
	}
}


実行結果:

---代入前---
コピー元の要素[1] :10
コピー先の要素[1] :10
異なるインスタンスのため Deepコピー

---代入後---
コピー元の要素[1] :50
コピー先の要素[1] :50
同じオブジェクトのため Shallowコピー

配列オブジェクトそのものは、プリミティブ型、オブジェクト型に関わらず Deepコピーされます

しかし、各配列要素が指すオブジェクトは Shallowコピーで作成されるため、

コピー先の配列要素の内容を変更するとコピー元の配列に影響します。


arraycopyメソッド

Systemクラスには配列をコピーする 「arraycopyメソッド」が用意されています。

arraycopyメソッドはコピー元の配列の範囲を指定して、コピー先の配列にコピーすることができます。

そのためコピー元とコピー先の配列のコピーする開始位置や要素の数を指定することができるメリットがあります。

配列をコピーする方法の中では、arraycopyメソッドが最も高速です。

arraycopyメソッドを使う場合、コピー先の値がコピー元の値で上書きされますので注意が必要です。


arraycopyメソッドはコピーする配列の要素がプリミティブ型のときは Deepコピー、オブジェクト型のとき Shallowコピーになるので注意が必要です。


arraycopyメソッドの使い方

System.arraycopy(コピー元の配列,コピー元の配列の開始位置,コピー先の配列,コピー先の配列の開始位置,長さ);


arraycopyメソッドを使ったサンプルコードを確認しましょう。

import java.util.Arrays;

public class Sample4 {
	public static void main(String[] args) {
		int[] original = { 10, 20, 30 };
		int[] replica = new int[3];
		System.arraycopy(original, 0, replica, 0, original.length);

		replica[0] = 100;
		System.out.println("---代入後--- ");
		System.out.println("コピー元 : " + Arrays.toString(original));
		System.out.println("コピー先 : " + Arrays.toString(replica));
	}
}


実行結果:

---代入後--- 
コピー元 : [10, 20, 30]
コピー先 : [100, 20, 30]

配列の要素がプリミティブ型のため Deepコピーになりました。

配列 originalの値の代入を行っても配列 replicaの値には影響が出ていないことが確認できました。


ArrayList

Javaで 「ArrayList」は配列と並びよく使われ、複数の値をとりまとめて管理することができます。

配列には格納できる要素の大きさが決まっています。

その為、あらかじめ決めた大きさを超える要素を格納すると、「IndexOutOfBoundsExceptionエラー」が発生します。

それに対し、ArrayListは要素数の大きさが決まっていません

また Javaの ArrayListには、int、booleanなどのプリミティブ型は入れられません。

ArrayListと配列は、機能的な特徴が似ていますが、宣言方法については大きく異なります。


ArrayListの使い方

ArrayList<> 変数名 = new ArrayList<>();


ArrayListのコピーには Shallowコピーと Deepコピーがあります。

Shallowコピーの場合

ArrayList<Object> コピー先の配列名 = コピー元の配列名;

Shallowコピーは代入する形で記述します。


ArrayListを使った Shallowコピーのサンプルコードを確認しましょう。

import java.util.ArrayList;

public class Sample5_1 {
	public static void main(String[] args) {
		ArrayList<String> src_list = new ArrayList<>();
		for (int i = 0; i < 3; i++) {
			src_list.add("ABC");
		}

		ArrayList<String> shallow_list = src_list;
		for (int i = 0; i < shallow_list.size(); i++) {
			shallow_list.set(i, "ZZZ");
		}

		System.out.println("コピー元 : " + src_list);
		System.out.println("コピー先 : " + shallow_list);
	}
}


実行結果:

コピー元 : [ZZZ, ZZZ, ZZZ]
コピー先 : [ZZZ, ZZZ, ZZZ]

src_listの要素は「ABC」でしたが Shallowコピーを行ったため、shallow_listの要素を「ZZZ」に変更すると src_listも影響を受け変更され、src_listの要素は「ZZZ」で処理が行われました。


Deepコピーの場合

ArrayList<Object> コピー先の配列名 = new ArrayList<>(コピー元の配列名);

Deepコピーの場合は new演算子を使いインスタンスを生成し、その引数にコピー元の配列を渡して記述します。


ArrayListを使った Deepコピーのサンプルコードを確認しましょう。

import java.util.ArrayList;

public class Sample5_2 {
	public static void main(String[] args) {
		ArrayList<String> src_list = new ArrayList<>();
		for (int i = 0; i < 3; i++) {
			src_list.add("ABC");
		}

		ArrayList<String> deep_list = new ArrayList<>(src_list);
		for (int i = 0; i < deep_list.size(); i++) {
			deep_list.set(i, "ZZZ");
		}

		System.out.println("コピー元 : " + src_list);
		System.out.println("コピー先 : " + deep_list);
	}
}


実行結果:

コピー元の配列 : [ABC, ABC, ABC]
コピー先の配列 : [ZZZ, ZZZ, ZZZ]

Deepコピーを行ったため、shallow_listの要素を「ZZZ」に変更しても src_listは影響を受けません。

そのため src_list配列の要素は「ABC」で処理が行われました。


まとめ

この記事では配列のコピーについて解説しました。

初めて学習するときは Shallowコピーと Deepコピーの違いと書き分けの理解が大変だと思います。

知らないままコピーの方法だけ覚えると、動作を行うときに思わぬ結果になることがあります。

コピーの仕方をいろいろと紹介しましたがどれを使うべきか悩まず、自分使いやすい方法で使うことができればいいと思います。


【著者】

松倉祥大

はじめまして。フォワードソフト株式会社の松倉です。
システムエンジニアとして働き始めたのが2020年4月です。経験や知識がない状態で入社した私は、フォワードソフトの教育研修で一からプログラミングの勉強をしました。教育研修を卒業後、Javaのプログラミングについて初学者向けの記事を共同で制作しています。
知識や経験はまだまだですが、これからいろんな職場で様々な経験しながら勉強していきたいと思っています。

よく読まれている記事
【Java】JSPでタグライブラリを使う(JSTL)

【Java】JSPでタグライブラリを使う(JSTL)

こんにちは。エンジニアの新田です!ここでは、システムエンジニアとして働いている私が、システム開発手法や開発言語について紹介していこうと思います。今回は、JSPの標準タグライブラリ「JSTL」について紹介します。Javaについて勉強している方、Webアプリケーションを構築したいと思っている方の参考になれば幸いです!関連記事リンク: 【Java】JSPの基本的な構文/【Java】JSPのアクションタグ

【Java】Stringクラス文字列を操作するメソッドの使い方まとめ!実例も紹介!

【Java】Stringクラス文字列を操作するメソッドの使い方まとめ!実例も紹介!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!そもそもStringとは何?『 String 』... Java言語において文字列のデータ型を指します。基本デ

【Java】文字列の置き換え(String#format)!エスケープシーケンスのまとめも!!

【Java】文字列の置き換え(String#format)!エスケープシーケンスのまとめも!!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!Stringクラスformatメソッドの文字列整形【java.utilパッケージ】Formatterクラスfo

【Java】文字列格納後に変更可能!?StringBufferクラスとStringBuilderクラス!

【Java】文字列格納後に変更可能!?StringBufferクラスとStringBuilderクラス!

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者にも✅、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!文字列を扱う3つのクラス【java.langパッケージ】java.langパッケージの文字列を扱うクラスにはS

【Java】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

【Java】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

こんにちは。新人エンジニアのサトウです。システムエンジニアとして駆け出したばかりですが、初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。プログラム初心者✅にも、プログラムに興味がある人✨も、短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!プリミティブ型と参照型プログラム開発では型を持った変数を使ってデータのやり取りをしますが、データ型によって仕様