Java深層複製方式

爲何須要深層複製

Object 的 clone() 方法是淺層複製(可是 native 很高效)。
另外,Java 提供了數組和集合的複製方法,分別是 Arrays.copy() 和 Collections.copy() 方法。
前者實際上使用了 System.arraycopy() 方法,二者其實也是淺層複製,過程相似於下面的 for 循環:數組

for(int i=0; i<len; i++){
  dest[i] = src[i];
}

因此當數組或集合中元素是對象時,只是作了引用的複製,指向的仍是堆中同一個對象。ide

 

通常有兩種深層複製方案

1)實現 Cloneable 接口性能

包裝 Object.clone() ,根據屬性類型深度 clone。this

這種方法,使用了 Object.clone() ,優勢是 native 方法性能好,缺點是實現太繁瑣。spa

/**
 * 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)對象序列化輸入輸出code

這種方法,強大且簡單,先寫到內存,再讀出來。對象

PS:單例對象最好也關注下是否有被序列化複製的風險。blog

/**
 * 深層複製方案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;
    }
View Code

 

參考閱讀:接口

https://www.jianshu.com/p/7aaaf884cc44內存

相關文章
相關標籤/搜索