Javaの裏技(オブジェクトのディープコピーのやり方)

Java は、すべて参照(ポインタ)の言語です

もう使う必要もないはずの、Javaの裏技(オブジェクトのディープコピー)です。

さて、Java は、すべて参照(ポインタ)の言語です。そのため、オブジェクトの中の値を変更するとすべての参照が変更されます。

少し、難しいですね。

例を挙げると、

Sample obj1 = new Sample();
obj1.hand = "右";	// 利き手を右とする
obj1.foot = "右";	// 利き足を右とする

Sample obj2 = obj1;
obj2.hand = "左";	// 利き手を左に変更

System.out.println(obj1.hand);
System.out.println(obj2.hand);

// -----
// 
// 出力結果:
// 
// 左
// 左

この場合、obj1 と obj2 は同じ実体を参照しているため、ともに「左」が出力されます。(難しい言葉で「シャローコピー」と言います。)

別々に new をすれば…

もし、obj1 と obj2 を別々のものにするのであれば、ともに new をすれば一応の解決はします。

Sample obj1 = new Sample();
obj1.hand = "右";	// 利き手を右とする
obj1.foot = "右";	// 利き足を右とする

Sample obj2 = new Sample();
obj2.hand = obj1.hand;		// 利き手をコピー
obj2.foot = obj1.foot;		// 利き足をコピー

obj2.hand = "左";	// 利き手を左に変更

System.out.println(obj1.hand);
System.out.println(obj2.hand);

// -----
// 
// 出力結果:
// 
// 右
// 左

さて、利き手は別々のものが表示されるようになりました。利き足はともに「右」です。(難しい言葉で「ディープコピー」と言います。)

今回は、プロパティが2個でしたので大したことはありませんが、もしも、プロパティが 数十個、もしくはオブジェクトの中にオブジェクトの参照を設定していた場合はどうでしょう。

すべての値をディープコピーするのは至難の業です。

(そもそも、ディープコピーをしなければならないようなコーディングをしていることに問題があります。)

ディープコピーを一発で行う裏技コード…

そんなときに(仕方なく)使う裏技が下記のメソッドです。どんなオブジェクトもディープコピーすることができます。

コピー元の全てのクラスで Serializable インターフェイスを実装してください。

/**
 * オブジェクトのディープコピーをします。
 * @param obj   コピー元オブジェクト
 * @return      ディープコピーをしたオブジェクト
 * @throws IOException  入出力例外
 * @throws ClassNotFoundException   例外
 */
public static Object deepCopy(Serializable obj)
        throws IOException, ClassNotFoundException {
    
    ByteArrayOutputStream bout = null;
    ObjectOutputStream out = null;
    ByteArrayInputStream bin = null;
    ObjectInputStream in = null;
    try {
        
        if (obj == null) {
            return null;
        }
        
        bout = new ByteArrayOutputStream();
        out = new ObjectOutputStream(bout);
        out.writeObject(obj);
        bin = new ByteArrayInputStream(bout.toByteArray());
        in = new ObjectInputStream(bin);
        return in.readObject();
    } finally {
        bout.close();
        out.close();
        bin.close();
        in.close();
        bout = null;
        out = null;
        bin = null;
        in = null;
    }
}

使い方は、簡単です。

例として、DeepCopySample クラスに上記メソッドを定義したとしましょう。

Sample obj1 = new Sample();
obj1.hand = "右";	// 利き手を右とする
obj1.foot = "右";	// 利き足を右とする

Sample obj2 = (Sample) DeepCopySample.deepCopy(obj1);	// オブジェクトのディープコピー
obj2.hand = "左";	// 利き手を左に変更

System.out.println(obj1.hand);
System.out.println(obj2.hand);

// -----
// 
// 出力結果:
// 
// 右
// 左

メソッドを呼び出した後に、無理やりキャストすることがポイントです。

これで、もとのオブジェクトの値を残しつつ、必要なプロパティのみ変更することができるようになりました。

<追記> こちらの方が簡単そう…

Apache Commons Langに SerializationUtils#cloneがありました。このライブラリを使った方が、良いでしょう。

要注意…!

このディープコピーメソッドは多用しないでください。

この裏技を使わなければいけない場合は、プログラム設計(コーディング)が複雑になりすぎているのです。プログラム設計を一度、見直すことをお勧めします。