Javaで配列をコピーする際、気を付けなければいけないポイントがあります。
シャーロンコピーとディープコピーの違いです。
この2つの挙動の違いを把握しておかないと、思わぬバグを埋め込んでしまうことなります。
今回は、シャーロンコピーとディープコピーの違いを、サンプルプラグラムをもとに説明していきます。
目次
環境情報
- OS:Windows10
- Java:1.8.0_60
シャーロンコピーとは?ディープコピーとは?
シャーロンコピーとディープコピーとは何でしょうか?
まずは、この2つのコピーの違いについて説明します。
シャーロンコピー
C言語を経験した人の場合は、アドレスコピーときくとピンとくるかと思います。
要素を指定するアドレスをポインタを使用してコピーしており、要素がもつ値はコピーしていないです。
ディープコピー
値コピー、と言い換えることができます。
シャーロンコピーとは異なり、アドレスではなく、要素そのものをコピーします。
シャーロンコピーの概念が理解し辛い人が多い気がします。
C言語を使った人はわかるかと思いますが、「ポインタ」のイメージですね。
シャーロンコピーとディープコピーのサンプルプログラム
それでは、さっそくですがサンプルプログラムです。
例として、以下のサンプルプログラムになります。
- コピー対象は、独自エントリクラス(Entry)の配列を「ArrayList」で保持する。
- シャーロンコピー→ディープコピーの順番でコピーを実施。
- コピー元配列の内容を表示。
サンプルプログラム
作成するクラスは2つになります。
「CopyTest.java」がコピーするメインクラス、「Entry.java」が配列要素となるエントリクラスです。
CopyTest.java
import java.util.ArrayList; public class CopyTest { // メイン処理 public static void main(String[] args){ System.out.print("start: CopyTest\r\n"); /** ①:エントリ内容の作成と確認 */ ArrayList<Entry> srcList = new ArrayList<Entry>(); Entry newEnt1 = new Entry(); newEnt1.setSei("Yamada"); newEnt1.setNa("Taro"); srcList.add(newEnt1); Entry newEnt2 = new Entry(); newEnt2.setSei("Suzuki"); newEnt2.setNa("jiro"); srcList.add(newEnt2); System.out.print("エントリ内容の作成確認\r\n"); dispList(srcList); /** ②:シャーロンコピー */ ArrayList<Entry> shtList = srcList; Entry entry = shtList.get(0); entry.setSei("Satou"); entry.setNa("Saburo"); System.out.print("リストをコピー確認\r\n"); dispList(srcList); /** ③:ディープコピー */ ArrayList<Entry> deList = new ArrayList<Entry>(srcList.size()); for (Entry ent : srcList) { deList.add(ent.clone()); } entry = deList.get(0); entry.setSei("Tanaka"); entry.setNa("Shiro"); System.out.print("リストをコピー確認\r\n"); dispList(srcList); System.out.print("end: CopyTest\r\n"); } private static void dispList(ArrayList<Entry> dispList){ for (Entry dispEnt : dispList) { System.out.print("sei = " + dispEnt.getSei() + "\r\n"); System.out.print("na = " + dispEnt.getNa() + "\r\n"); } } }
Entry.java
public class Entry implements Cloneable { private String sei; private String na; // 姓の設定 public void setSei(String sei) { this.sei = sei; } // 名の設定 public void setNa(String na) { this.na = na; } // 姓の取得 public String getSei() { return this.sei; } // 名の取得 public String getNa() { return this.na; } // クーロン public Entry clone() { try { return (Entry) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); } } }
シャーロンコピーの実行結果
サンプルプログラムの②の部分がシャーロンコピーのサンプルプログラムです。
シャーロンコピーを実行した結果は以下になります。
sei = Satou na = Saburo sei = Suzuki na = jiro
実際にシャーロンコピーしている部分は以下です。
ArrayList<Entry> shtList = srcList;
シャーロンコピーしたコピー先リスト(shtList)の内容を変更しています。
エントリの1つ目について、以下のように変更しています。
・Sei=Satou ・Na=Saburo
シャーロンコピーしたコピー先リスト(shtList)を変更したにも関わらず、コピー元のリスト(srcList)が変更されています。
これはなぜかというと、コピーしたのはあくまでアドレスであるシャーロンコピーをおこなっているからになります。
変更先リストを変更しているにも関わらず変更元リストが変更されている、という事です。
ディープコピーの実行結果
サンプルプログラムの③の部分がディープコピーのサンプルプログラムです。
ディープコピーを実行した結果は以下になります。
sei = Satou na = Saburo sei = Suzuki na = jiro end: CopyTest
実際のディープコピーは、「Entry」クラスのcloneメソッドになります。
ArrayList<Entry> deList = new ArrayList<Entry>(srcList.size()); for (Entry ent : srcList) { deList.add(ent.clone()); }
要素を完全にコピーしています。
完全にコピーした要素に対して変更しているので、変更元の配列要素は変更されていないです。
これがディープコピーになります。
通常は、上記のようにcloneメソッドを準備して、要素を完全コピーする実装を準備しておきます。
よく、様々なウェブサイトで以下のように実装すればディープコピーされると記載されているのですが、それは間違いです。
気を付けてください。
ArrayList<Entry> deList = new ArrayList<Entry>(srcList);
まとめ
ディープコピーとシャーロンコピーの違いを正しく理解してプログラミングしましょう!
よくあるのが、ディープコピーしていると思っていたがシャーロンコピーで、コピー元の要素が変更されてしまった!というパターンです。
思わぬバグを作りこんでしまう事になります。