サイトアイコン たーちゃんの「ゼロよりはいくらかましな」

【java】オブジェクトをコピーする方法色々

javaでオブジェクトコピーというと、

どのようなコードをイメージされますか?

 

SampleDto sample1 = new SampleDto();
SampleDto sample2 = sample1;

 

こんな感じですか?

javaでは上記コードではオブジェクトは参照渡しとなるため、

sample2はsample1と同じものを指します。

 

SampleDto sample1 = new SampleDto();
sample1.setCol1("A");

SampleDto sample2 = sample1;
sample2.setCol1("B");

System.out.println("Col1 -> " + sample1.getCol1()); // ←ここではBになります

 

sample2のcol1をBとセットすると、

同じものを指すsample1の方もcol1はBとなります。

 

では、オブジェクトをコピーするにはどうしたらよいでしょうか??

 

 

 

 

 

Cloneableインターフェースを実装する

Cloneableインターフェースという、実装のないインターフェースがあります。

Serializableと同じように、Cloneできることができるマークのようなものです。

このインターフェースを実装すると、ObjectクラスにあるCloneメソッドを

overrideすることでオブジェクトをコピーすることができます。

 

例えばCloneableを実装したDTOはこんな感じになります。

この中のcloneメソッドがオブジェクトをコピーしている処理になります。

 

package jp.co.tarchan.deepcopy;

/**
 * Cloneable DTO.
 */
public class ClonableSampleDto implements Cloneable {

    private String col1;

    private String col2;

    private Integer col3;

    public String getCol1() {
        return col1;
    }

    public void setCol1(String col1) {
        this.col1 = col1;
    }

    public String getCol2() {
        return col2;
    }

    public void setCol2(String col2) {
        this.col2 = col2;
    }

    public Integer getCol3() {
        return col3;
    }

    public void setCol3(Integer col3) {
        this.col3 = col3;
    }

    @Override
    public ClonableSampleDto clone() throws CloneNotSupportedException {
        return (ClonableSampleDto) super.clone();
    }
}

 

実際の使い方とCloneableを実装しない場合の挙動

同じ構成で以下のように、Cloneableを実装しないDTOを用意して、

実装有無での動作の違いを見てましょう。

 

package jp.co.tarchan.deepcopy;

/**
 * Not Cloneable DTO.
 */
public class NotClonableSampleDto {

    private String col1;

    private String col2;

    private Integer col3;

    public String getCol1() {
        return col1;
    }

    public void setCol1(String col1) {
        this.col1 = col1;
    }

    public String getCol2() {
        return col2;
    }

    public void setCol2(String col2) {
        this.col2 = col2;
    }

    public Integer getCol3() {
        return col3;
    }

    public void setCol3(Integer col3) {
        this.col3 = col3;
    }

    @Override
    public NotClonableSampleDto clone() throws CloneNotSupportedException {
        return (NotClonableSampleDto) super.clone();
    }
}

 

 

 

 

 

 

 

こちらが主処理になります。

package jp.co.tarchan.deepcopy;

/**
 * Deepコピーするサンプル.
 */
public class DeepCopySample {

    /**
     * 主処理.
     * @param args 実行引数
     */
    public static void main(String[] args) throws Exception {

        // Cloneableを実装したDTO
        ClonableSampleDto clonableSampleDto = new ClonableSampleDto();
        clonableSampleDto.setCol1("123");
        clonableSampleDto.setCol2("456");
        clonableSampleDto.setCol3(1);

        // 複製する
        ClonableSampleDto clonedClonableSampleDto = clonableSampleDto.clone();

        System.out.println("Cloneable実装 複製前hashCode" + clonableSampleDto.toString());
        System.out.println("Cloneable実装 複製後hashCode" + clonedClonableSampleDto.toString());

        // Cloneableを実装していないDTO
        NotClonableSampleDto notClonableSampleDto = new NotClonableSampleDto();
        notClonableSampleDto.setCol1("abc");
        notClonableSampleDto.setCol2("def");
        notClonableSampleDto.setCol3(2);

        // 複製する
        NotClonableSampleDto clonedNotClonableSampleDto = notClonableSampleDto.clone();

        System.out.println("Cloneable未実装 複製前hashCode" + notClonableSampleDto.toString());
        System.out.println("Cloneable未実装 複製後hashCode" + clonedNotClonableSampleDto.toString());
    }
}

 

 

これを実行すると・・・

Cloneable実装 複製前hashCodejp.co.tarchan.deepcopy.ClonableSampleDto@7f13d6e
Cloneable実装 複製後hashCodejp.co.tarchan.deepcopy.ClonableSampleDto@51cdd8a
Exception in thread "main" java.lang.CloneNotSupportedException: jp.co.tarchan.deepcopy.NotClonableSampleDto
	at java.base/java.lang.Object.clone(Native Method)
	at jp.co.tarchan.deepcopy.NotClonableSampleDto.clone(NotClonableSampleDto.java:40)
	at jp.co.tarchan.deepcopy.DeepCopySample.main(DeepCopySample.java:33)

 

こんな感じでエラーログが出力されます。

要するに、Cloneableを実装していないのにcloneはできないよということですね。

 

では、Cloneableを実装した方は、hashcodeの違う別のオブジェクトとして、

cloneが成功し、複製ができていることがわかりますね。

 

 

Cloneableインターフェースを実装できないときは?

ですが、たとえばjarの中にあってCloneableを自分で実装できないものを

複製しなければいけなかったりしたらどうしたらよいでしょうか?

 

コピー対象がSerializableを実装しているとき

コピー対象がSerializableを実装しているのであれば、

シリアライズ・デシリアライズすることでコピーすることができます。

 

例えば、こんな感じでメソッドを用意します。

    /**
     * オブジェクト 汎用 deepcopy処理.
     * @param obj コピー対象オブジェクト
     * @return コピーしたオブジェクト
     * @throws Exception 例外
     */
    @SuppressWarnings("unchecked")
    public static <T> T deepCopy(T obj) throws Exception {

        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteOut);
        out.writeObject(obj);

        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
        ObjectInputStream in = new ObjectInputStream(byteIn);

        return (T) in.readObject();
    }

 

このメソッドにコピー元を渡してあげればTで総称型で引数・戻り値を定義しているので、

同じ型で受けることができますね。

 

Serializableも実装できないときは?

この場合は、複製オブジェクトをnewして、地道にコピーといきそうですが、

apache.commonsのBeanUtilsを使用すれば、地道なコピーはかなり省力化できます。

 

BeanUtilsではBeanUtils.cloneBean(Object obj)か、

BeanUtils.copyProperties(dest, org)を使用すればOKです。

 

 

 

まとめ

と、いうことで色々なコピーの方法でした。

どんな場合でもコピーは可能だということはおわかりいただけたかと思います。

場面に応じて使い分けてみてくださいねー。

 

それでは!

 

 


にほんブログ村


人気ブログランキング

モバイルバージョンを終了