検索

キーワード


【Java】深いコピーと浅いコピー?『インスタンスのコピー』を詳しく解説!

  • 公開日:2020-10-22 23:41:15
  • 最終更新日:2021-01-25 16:23:02
【Java】深いコピーと浅いコピー?『インスタンスのコピー』を詳しく解説!

こんにちは。新人エンジニアのサトウです。

システムエンジニアとして駆け出したばかりですが、

初心者なりの視点でわかりやすい記事を心がけていますので参考になればうれしいです。


プログラム初心者✅にも、プログラムに興味がある人✨も、

短い時間で簡単にできますのでぜひこの記事を読んで試してみてください!


インスタンスのコピー

Javaでインスタンスをコピーするには

ディープコピー(深いコピー)

シャローコピー(浅いコピー)

の二つの方法を検討する必要があります。


配列のコピーについては【Java】配列のコピーで紹介しています。


- シャローコピー

シャローコピーは、実体のコピーを行わずにインスタンスをコピーする方法です。

コピーしたインスタンス自体は、元のインスタンスとは別の扱いであるものの、参照先は同じになります。

ですのでインスタンスのメンバ変数を変更した場合は、もう一方のインスタンスにも影響が及びます。

シャローコピーのイメージ図


- 方法

「=演算子」を使ってインスタンスを代入する事で、シャローコピーを行う事ができます。

シャローコピーを下記のサンプルコードで確認しましょう。

サンプルコード

import java.util.ArrayList;

//インスタンス用クラス
class DuplicateObject {

	public int value = 0;
	public String source = "あいさつ";
	public ArrayList<Integer> list = new ArrayList<>();

	//出力メソッドの定義
	public void dump(String name) {
		System.out.println("----[" + name + "]----");
		System.out.println("value:" + value);
		System.out.println("source:" + source);
		System.out.println("list:" + list.toString());
	}
}

public class Test {
	public static void main(String[] args) {

		//インスタンスを生成
		DuplicateObject origin = new DuplicateObject();
		origin.value = 1;
		origin.source = "おはよう";
		origin.list.add(10);

		//インスタンスのシャローコピー
		DuplicateObject copy = origin;
		copy.value = 2;
		copy.source = "おやすみ";
		copy.list.add(20);

		//出力メソッド実行
		origin.dump("コピー元");
		copy.dump("コピー先");
	}
}

出力結果

----[コピー元]----
value:2
source:おやすみ
list:[10, 20]

----[コピー先]----
value:2
source:おやすみ
list:[10, 20]


copyのフィールドを変更しただけですが、出力結果ではoriginのフィールドも変更されています。

これが同じ実体(データ)を参照しているということです。


- ディープコピー

ディープコピーとは、実体も含めてインスタンスをコピーする方法です。

参照先の実体も含めてコピーし新しいインスタンスを生成するので、

一方に変更を加えても、もう一方に影響を与えません。

ディープコピーのイメージ図


- 方法

ディープコピーを行うには、

cloneメソッドを利用した方法とシリアライズオブジェクトを使った方法の二通りの仕方があります。


次の項で確認していきましょう!

ディープコピー使用の詳細


- cloneメソッド

cloneメソッドはオブジェクト(インスタンス)のコピー(クローン)を作るメソッドとしてObjectクラスに実装されています。

protected Object clone()


Object.cloneの特徴


・protected修飾子が付いている

・戻り値がObject型である

・インスタンスをコピーする際はClonableインターフェイスを実装する必要がある

⇒Clonableインターフェイスは、マーカーインターフェイスであり、複製可能ということを表しています。



cloneメソッドを使ってディープコピーする場合は、対象のクラスでcloneメソッドが適切に実装されている必要があります。

cloneメソッドを使ったディープコピーのサンプルコードを確認しましょう。

サンプルコード

import java.util.ArrayList;
/*
 * CloneableObjectクラス:説明用のためsetter/getter を省略
 */
class CloneableObject implements Cloneable {

	public int value = 0;
	public String source = "あいさつ";
	public ArrayList<Integer> list = new ArrayList<>();

	/*
	 * cloneメソッドの定義
	 * public修飾子、自分自身の型を返り値とする
	 * Objectクラスのcloneメソッドが、
	 * CloneNotSupportedExceptionを投げる可能性を考慮する
	 */
	public CloneableObject clone() throws CloneNotSupportedException {

		CloneableObject o = (CloneableObject)super.clone();
		/* "super.clone()"の戻り値はObject型なので継承クラスの型に変更
		 * ⇒(CloneableObject)を付ける!
		 */
		return o;
	}

	//出力メソッドの定義
	public void dump(String name) {
		System.out.println("----[" + name + "]----");
		System.out.println("value:" + value);
		System.out.println("source:" + source);
		System.out.println("list:" + list.toString());
	}
}

public class Test {
	public static void main(String[] args) throws CloneNotSupportedException {

		//インスタンスを生成
		CloneableObject origin = new CloneableObject();
		origin.value = 1;
		origin.source = "おはよう";
		origin.list.add(10);

		//インスタンスのディープコピー
		CloneableObject copy = origin.clone();
		copy.value = 2;
		copy.source = "おやすみ";
		copy.list.add(20);

		//出力メソッド実行
		origin.dump("コピー元");
		copy.dump("コピー先");
	}
}

出力結果

----[コピー元]----
value:1
source:おはよう
list:[10, 20]

----[コピー先]----
value:2
source:おやすみ
list:[10, 20]

※listだけがシャローコピー


- cloneメソッド(参照型変数のディープコピーの注意)

前項のcloneメソッドを使用したサンプルコードでの出力結果で、おかしいなと思ったところがあったと思います。

◆前項の出力結果

----[コピー元]----
value:1 source:おはよう list:[10, 20]

----[コピー先]----
value:2 source:おやすみ list:[10, 20]


前項の実装ではArrayList型(参照型)の変数がディープコピーされていません。

実はインスタンス元のクラスにcloneメソッドを定義しただけでは、

参照型変数はシャローコピーされてしまいます。

colonメソッドで参照型変数をディープコピーするには、個別実装が必要になります。

※プリミティブ型、イミュータブルオブジェクト(String,Number)は個別実装不要


インスタンス元クラスのcloneメソッド内に以下のような個別実装を行い、ディープコピーをします。

 - 参照型変数それぞれにcloneメソッドを実装する

	// cloneメソッドの定義
	public CloneableObject clone() throws CloneNotSupportedException {

		CloneableObject o = (CloneableObject)super.clone();

		//個別実装
		o.list = (ArrayList<Integer>)list.clone();

		return o;
	}

上記の個別実装を前項のサンプルコードに含めれば、参照型の変数もディープコピーすることができるようになります。

◆個別実装後の出力結果

----[コピー元]----
value:1
source:おはよう
list:[10]

----[コピー先]----
value:2
source:おやすみ
list:[10, 20]


- シリアライズオブジェクトを使ったコピー


シリアライズの概要については以下の記事で紹介しています。

参考:【Java】 シリアライズの紹介!


シリアライズ化した後、オブジェクトを再定義することでディープコピーができます。

以下のサンプルコードでシリアライズ後に変更を加えたメンバ変数を確認してみましょう。

サンプルコード

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;

class SerializeObject implements Serializable {

	public int value = 0;
	public String source = "あいさつ";
	public ArrayList<Integer> list = new ArrayList<>();

	//出力メソッドの定義
	public void dump(String name) {
		System.out.println("----[" + name + "]----");
		System.out.println("value:" + value);
		System.out.println("source:" + source);
		System.out.println("list:" + list.toString());
	}
}

public class Test {
	public static void main(String[] args) throws Exception {

		// シリアライズ対象クラスをインスタンス化
		SerializeObject o1 = new SerializeObject();
		o1.value = 1;
		o1.source = "おはよう";
		o1.list.add(10);

		// ObjectOutputStream でシリアライズ
		ByteArrayOutputStream stream= new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(stream);
		out.writeObject(o1);

		//ObjectInputStream でデシリアライズ
		ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(stream.toByteArray()));

		//メンバ変数を変更
		SerializeObject o2 = (SerializeObject) in.readObject();
		o2.value = 2;
		o2.source = "おやすみ";
		o2.list.add(20);

		//出力メソッド実行
		o1.dump("シリアライズ実行前");
		o2.dump("シリアライズ実行後");
	}
}

出力結果

----[シリアライズ実行前]----
value:1
source:おはよう
list:[10]

----[シリアライズ実行後]----
value:2
source:おやすみ
list:[10, 20]

シリアライズ化することで、ディープコピーをすることが出来ました。


【著者】

新人IT勉強家SATO

読者を始め私自身も知りたい!と思うような知って得するプログラミング知識やIT情報を日々発信しております。
憧れの人物はコリン・ファース、渡部篤郎さん、仮面ライダー(子供のころから)
かような方々に負けない大人で在れる様、プログラミングを始め興味関心を持ったことを毎日勉強中デス !!

よく読まれている記事
【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】値?変数?型??しっかり解説!『データ型(プリミティブ型と参照型)』

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