两种 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 方法性能好,缺点是实现太繁琐。

java深度复制list java 深度复制_序列化

java深度复制list java 深度复制_java_02

/**
 * 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 + "]";
    }
}

View Code

 

2)对象序列化输入输出

这种方法,强大且简单,先写到内存,再读出来。

PS:单例对象最好也关注下是否有被序列化复制的风险。

java深度复制list java 深度复制_序列化

java深度复制list java 深度复制_java_02

/**
 * 深层复制方案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;
    }