Java オブジェクトのclone - シャローコピーとディープコピー

2009/06/04 11:21Update
TAGS: Java | クローン | コピー | 複製 | clone()

Java オブジェクトのディープコピーの実装方法をサンプルから学びます。

Javaクローン(clone)の概要と利用方法について、次の記事をご参照ください。
Java オブジェクトのclone 概要

まず、シャローコピーとディープ(deep)コピーとは何かから説明したいと思います。

シャローコピー はshallow copyのカタカナで、浅い複製という意味です。
ディープコピー はdeep copyのカタカナで、深い複製という意味です。

では、いったい何か浅い・深いとしているのでしょうか。

サンプルからみてみましょう。

サンプルからわかるJavaオブジェクトのシャローコピー



Java オブジェクトのclone 概要
記事のサンプルを少し修正してみます。
TestClone.java
public class TestClone {
    public static void main(String[] args) {
        //コピー元オブジェクト
        MyClone myClone1 = new MyClone("clone1");
        //String, boolean, String型のフィールドに値を設定
        myClone1.setBoolValue(true);
        myClone1.setIntValue(100);
        
        //Listフィールド
        List <Element>listValue = new ArrayList<Element>();
        listValue.add(new Element("ListElement1"));
        listValue.add(new Element("ListElement2"));
        listValue.add(new Element("ListElement3"));
        myClone1.setListValue(listValue);
        
        //String, boolean, String以外の型のフィールド
        Element element1 = new Element("element1");
        myClone1.setElement(element1);
        
        
        //クローン(コピー先オブジェクト)
        MyClone myClone2 = (MyClone)myClone1.clone();
        
        if (myClone2 != null) {
            
            //String, int, boolean型
            System.out.println("myClone2.name=" + myClone2.getName() 
                    + " myClone2.boolValue=" + myClone2.isBoolValue() 
                    + " myClone2.intValue=" + myClone2.getIntValue() );
            
            //List<Element>とElement型
            List clonedList = myClone2.getListValue();
            Element element2 = myClone2.getElement();

            System.out.println("myClone2.listValue.equals(myClone1.listValue):" + clonedList.equals(myClone1.getListValue()));
            System.out.println("myClone2.listValue.size():" + clonedList.size());
            System.out.println("myClone2.element.equals(myClone1.element):" + element2.equals(element1));
            System.out.println("myClone2.element.name:" + element2.getName());
            
            //コピー先オブジェクトの「List<Element>とElement型」のフィールド値を修正してみる
            clonedList.add("ListElement4");
            element2.setName("Element2");
            
            System.out.println("=====myClone2.listValueとmyClone2.elementが修正したよ=====");
            
            System.out.println("myClone1.listValue.size():" + listValue.size());
            System.out.println("myClone1.element.name:" + element1.getName());
            
        } else {
            System.out.println("Clone Not Supported");
        }        
        
    }

}

class MyClone implements Cloneable {
    private int intValue;
    private boolean boolValue;
    private String name;
    private List <Element>listValue;
    private Element element;

    public MyClone(String name) {
        this.name = name;
    }

    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }
    ...//setterとgetterメソッド(略)
}

class Element implements Cloneable  {
    private String name;
    
    public Element (String name) {
        this.name = name;
    }

    ...//setterとgetterメソッド(略)
}


実行結果:
C:\clone>javac *.java
C:\clone>java TestClone
myClone2.name=clone1 myClone2.boolValue=true myClone2.intValue=100
myClone2.listValue.equals(myClone1.listValue):true
myClone2.listValue.size():3
myClone2.element.equals(myClone1.element):true
myClone2.element.name:element1
=====myClone2.listValueとmyClone2.elementが修正したよ=====
myClone1.listValue.size():4
myClone1.element.name:Element2

ご覧の通り、
clonedList.equals(myClone1.getListValue())とelement2.equals(element1)はtrueを返しています。
clonedListを修正した場合、myClone1.listValueも修正されたことも確認できています。
即ち、コピー元とコピー先オブジェクトのフィールド値は同じオブジェクトを参照していると言えます。

結論:シャローコピーとディープコピー


Java Object#clone()は単なるシャローコピーであり、コピー先の参照型のフィールドはコピー元の参照先と同じになります。
そのため、コピー先の参照型のフィールド値を変更するとコピー元まで変更されてしまいます。

その反面、コピー先の参照型のフィールド値を変更してもコピー元は変更されないのはディープコピーです。

ディープコピーの実装方法


では、ディープコピー機能をどうやって実装できるの?
答えは、オーバーライドしたclone()メソッドに、参照型のフィールドにもclone()処理をかけます。
もちろん、フィールドの型はCloneableインタフェースを実装しなければなりません。

例:
class MyClone implements Cloneable {
    ...
    public Object clone() {
        try {
            MyClone myClone = (MyClone)super.clone();
            //参照型のフィールドにもclone()
            myClone.element = this.element.clone();

            //参照型はListやMapの場合
            myClone.listValue = new ArrayList();
            for (Element ele:this.listValue) {
                myClone.listValue.add(ele.clone());
            }

            return myClone;
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }
    ...
}

//ElementクラスにもCloneableを実装させる
class Element implements Cloneable  {
    ...
    public Element clone() {
        try {
            return (Element)super.clone();
        } catch (CloneNotSupportedException e) {
            return null;
        }
    }
}

.

有关作者
Syboos.jp編集長システム設計や開発、保守運営などを行ってます。オープンソース技術に興味があります。

Sponsored Link


Comments

用户名 (required)

Email (will not be published) (required)

URL

Evaluation