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です。
まとめ
と、いうことで色々なコピーの方法でした。
どんな場合でもコピーは可能だということはおわかりいただけたかと思います。
場面に応じて使い分けてみてくださいねー。
それでは!
20代前半までは東京で音楽をやりながら両手の指以上の業種でアルバイト生活をしていましたが、某大手プロバイダのテレアポのバイトでPCの知識の無さに愕然とし、コンピュータをもっと知りたい!と思ったことをきっかけに25歳の時にITの世界に未経験で飛び込みました。
紆余曲折を経て、現在は個人事業主としてお仕事させていただいており、10年ほどになります。
web制作から企業システム構築、ツール開発など、フロントエンドもバックエンドもサーバーもDBAも依頼があれば何でもやってきた雑食系エンジニアです。
今風にいうとフルスタックエンジニアということになるのでしょうか??
→ 詳細プロフィールというか、生い立ちはこちら
→スキルシートをご覧になる場合はこちら
→お仕事のご依頼やお見積りなどお問い合わせはこちらから!