EeBlog(テクニカルブログ)

第29回 シャローコピーとディープコピー

今回のテーマは「シャローコピーとディープコピー」です。

まずは「シャローコピーとは何ぞや」というところから始めましょう。 シャロー(shallow )とは「浅い」という意味です。 では、シャローコピーはいったい何を「浅い」としているのでしょうか。 この疑問を解決するために、前回説明したcloneメソッドを利用して説明していきます。 というのは、JavaのObjectクラスのcloneメソッドの実装はシャローコピーだからです。 前回cloneメソッドを使用した際にシャローコピーが行われていたのですね。

それでは前回と似たようなソースで確認していきましょう。 まずはいつものSportsインターフェースとその具象クラスです。

public interface Sports {     
    void play();
}

public class Badminton implements Sports { 
    @Override    
    public void play() {
        System.out.println("バドミントンして遊びます。");  
    } 
}

Playerクラスには、シャローコピーを説明しやすくするために、getterを一つ追加しました。
前回と同じくCloneableインターフェースをインプリメントしています。

public class Player implements Cloneable { 
    private Sports sports;

    public Player(Sports sports) {      
        this.sports = sports;   
        System.out.println("コンストラクタです。"); 
    }

    public Sports getSports() {  
        return sports;  
    }

    public void play() {     
        System.out.println("ウォームアップします。");

        sports.play();

        System.out.println("クールダウンします。");
    } 
}

Mainクラスでは実際に複製を行い、シャローコピーを理解するためのインスタンスの比較を行っています。 なお、今回は汎用的なclone生成メソッドを用意しました。 最後のキャスト時に型の安全性に関して、コンパイラから警告が出されてしまうので、SuppressWarningsアノテーションにより警告されないようにしています。 cloneメソッドがオーバーライドされていない限り、シャローコピーされます。

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {  
        Player player = new Player(new Badminton());     
        player.play();
        System.out.println();

        Player clonePlayer = createClone(player);   
        clonePlayer.play();      
        System.out.println();

        System.out.print("Playerのインスタンスが同じ:"); 
        System.out.println(player == clonePlayer);      
        System.out.print("Sportsのインスタンスが同じ:");  
        System.out.println(player.getSports() == clonePlayer.getSports()); 
    }

    @SuppressWarnings("unchecked") 
    public static <T extends Cloneable> T createClone(T t) throws Exception { 
        if (t == null) { 
            return null; 
        }

        Method cloneMethod = Object.class.getDeclaredMethod("clone"); 
        cloneMethod.setAccessible(true); 
        return (T) cloneMethod.invoke(t); 
    } 
}

このプログラムを実行したら、複製を行ったPlayerのインスタンスは異なりますが、PlayerクラスのフィールドのSportsのインスタンスが同じという結果が出ます。 どうしてこうなるのでしょうか。

 

実はシャローコピーの場合、int等のプリミティブ型はそのままコピーされますが、参照型に関しては、あくまで参照がコピーされるだけであって、参照先のインスタンスがコピーされるわけではないのです。 じゃあ、参照先のインスタンスごとコピーする場合は何かというと、それがディープコピーなのです。 参照をコピーするのか、参照先のインスタンスごとコピーするのかについて、浅い深いと言っているわけですね。

シャローコピーとディープコピーの違いをしっかり理解して、状況に応じて適切に使い分けられるようになりましょう。