Java深层复制方式
为什么需要深层复制
Object 的 clone() 方法是浅层复制(但是 native 很高效)。
另外,Java 提供了数组和集合的复制方法,分别是 Arrays.copy() 和 Collections.copy() 方法。
前者实际上使用了 System.arraycopy() 方法,两者其实也是浅层复制,过程类似于下面的 for 循环:
for(int i=0; i<len; i++){ dest[i] = src[i]; }
所以当数组或集合中元素是对象时,只是做了引用的复制,指向的还是堆中同一个对象。
一般有两种深层复制方案
1)实现 Cloneable 接口
包装 Object.clone() ,根据属性类型深度 clone。
这种方法,使用了 Object.clone() ,优点是 native 方法性能好,缺点是实现太繁琐。
/** * 0 实现 Cloneable 接口 * 1 包装 super.clone(),提供 public 方法 * 2 默认的是浅层复制 * @author Super * */ public class shallowCloneTest { public static void main(String[] args) { Resource0 r0 = new Resource0(0, "资源1号"); Resource0 r1 = new Resource0(1, "内部资源"); r0.setInnerResource(r1); //验证克隆 Resource0 r2 = r0.shallowClone(); System.out.println(r0); System.out.println(r2); System.out.println(r1==r2); //false //验证浅度克隆 r2.getInnerResource().setId(7); System.out.println(r1); //受影响了 } } /** * 深层复制方案一:包装 clone,引用变量继续 clone * @author Super * */ public class DeepCloneTest1 { public static void main(String[] args) { Resource0 r0 = new Resource0(0, "资源1号"); Resource0 r1 = new Resource0(1, "内部资源"); r0.setInnerResource(r1); //验证克隆 Resource0 r2 = r0.deepClone(); System.out.println(r0); System.out.println(r2); System.out.println(r1==r2); //false //验证深度度克隆 r2.getInnerResource().setId(7); System.out.println(r1); //不受影响 } } public class Resource0 extends BaseVo implements Cloneable { private static final long serialVersionUID = 1L; private Integer id; private String name; private Resource0 innerResource; public Resource0(Integer id, String name) { super(); this.id = id; this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Resource0 getInnerResource() { return innerResource; } public void setInnerResource(Resource0 innerResource) { this.innerResource = innerResource; } /** * 浅层复制 * @return */ public Resource0 shallowClone(){ Resource0 r = null; try { r = (Resource0)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return r; } /** * 深层复制 * @return */ public Resource0 deepClone(){ Resource0 r = null; try { r = (Resource0)super.clone(); r.innerResource = (Resource0) innerResource.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return r; } @Override public String toString() { return "Resource0 [id=" + id + ", name=" + name + ", innerResource=" + innerResource + "]"; } }
2)对象序列化输入输出
这种方法,强大且简单,先写到内存,再读出来。
PS:单例对象最好也关注下是否有被序列化复制的风险。
/** * 深层复制方案2:输入输出序列化 * @author Super * */ public class DeepCloneTest2 { public static void main(String[] args) { Resource0 r0 = new Resource0(0, "资源1号"); Resource0 r1 = new Resource0(1, "内部资源"); r0.setInnerResource(r1); //验证克隆 Resource0 r2 = (Resource0) IOUtil.deepClone(r0); System.out.println(r0); System.out.println(r2); System.out.println(r1==r2); //false //验证深度度克隆 r2.getInnerResource().setId(7); System.out.println(r1); //不受影响 } } /** * 深层复制序列化 vo * @param src * @return dest * @throws IOException * @throws ClassNotFoundException */ public static BaseVo deepClone(BaseVo src) { ByteArrayOutputStream bo = null; ObjectOutputStream out = null; ObjectInputStream in = null; BaseVo dest = null; try{ try{ //对象写入内存 bo = new ByteArrayOutputStream(); out = new ObjectOutputStream(bo); out.writeObject(src); //从内存中读回来 in = new ObjectInputStream(new ByteArrayInputStream(bo.toByteArray())); dest = (BaseVo) in.readObject(); }finally{ //使用 finally 关闭资源 if(in!=null){ in.close(); } if(out!=null){ out.close(); } if(bo!=null){ bo.close(); } } //使用 catch 块统一捕捉资源 } catch(IOException | ClassNotFoundException ex){ ex.printStackTrace(); } return dest; }
参考阅读:
https://www.jianshu.com/p/7aaaf884cc44