天天看點

java中的淺拷貝與深拷貝詳解本文将由淺入深的講解下作者學到的java中的拷貝知識點,其中的遇到的一些坑和重要知識點也會一一點出。作者菜雞一頭,如有錯誤,非常感謝您的指正!

本文将由淺入深的講解下作者學到的java中的拷貝知識點,其中的遇到的一些坑和重要知識點也會一一點出。作者菜雞一頭,如有錯誤,非常感謝您的指正!

如果我們想實作一個對象的拷貝,那麼不涉及JDK提供的copy類最簡單的我們可以想到的是再次new出來一個這個類的執行個體,然後調用get和set方法去進行指派,但是這樣即使自定義抽取拷貝方法代碼重用度也是極低的,隻能适用于本項目。

其實JDK已經為我們提供好了對于對象的克隆方法。我們可以通過檢視API文檔中Object類的介紹或者通過在IDE中Ctrl+滑鼠左鍵點進Object類檢視,我們會發現有一個clone的native修飾方法(native修飾的java方法為JNI本地方法接口,一般都是C語言實作),又由于所有類都是繼承于Object類的,是以每個類都是有這個方法的。

但是我們會發現這個方法是protected修飾的,這樣根據下表我們可以得出protected的通路範圍,這就意味着我們想要調用這個方法就必須去重寫這個方法。

public protected default private
同一個類
同一個包 X
子類 X X
不同包 X X X

并且根據JDK規範,想要調用clone方法來實作克隆就必須所要進行clone的類實作Cloneable接口,否則調用clone方法時會報java.lang.CloneNotSupportedException異常

那麼下面将建立一個房屋類和一個窗戶的類,然後通過這房屋類去重寫clone方法來實作對房屋對象進行拷貝:

package cn.xb.entity;

import java.util.Date;

/**
 * 房屋
 * @author binxu
 *
 */
public class House implements Cloneable {
    //房子顔色
    private String color;
    //房子高度
    private Integer height;
    //房子擁有着姓名
    private String owner;
    //窗戶
    private Window window;
    //建造日期
    private Date manufactureDate;

    public Date getManufactureDate() {
        return manufactureDate;
    }
    public void setManufactureDate(Date manufactureDate) {
        this.manufactureDate = manufactureDate;
    }
    public Window getWindow() {
        return window;
    }
    public void setWindow(Window window) {
        this.window = window;
    }
    public String getColor() {
        return color;
    }
    @Override
    public String toString() {
        return "House [color=" + color + ", height=" + height + ", owner=" + owner + ", window=" + window
                + ", manufactureDate=" + manufactureDate + "]";
    }
    public void setColor(String color) {
        this.color = color;
    }
    public Integer getHeight() {
        return height;
    }
    public void setHeight(Integer height) {
        this.height = height;
    }
    public String getOwner() {
        return owner;
    }
    public void setOwner(String owner) {
        this.owner = owner;
    }
    /**
     * 這裡要注意,Object類中的clone方法為protected修飾,我們想要在非本包調用就必須将protected修飾符改為public,這樣滿足重寫規範
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

}
package cn.xb.entity;
/**
 * 窗戶
 * @author binxu
 *
 */
public class Window {
    //品牌
    private String pinpai;
    //是否拉伸型
    private Boolean isStretch;
    public String getPinpai() {
        return pinpai;
    }
    public void setPinpai(String pinpai) {
        this.pinpai = pinpai;
    }
    public Boolean getIsStretch() {
        return isStretch;
    }
    public void setIsStretch(Boolean isStretch) {
        this.isStretch = isStretch;
    }
    @Override
    public String toString() {
        return "Window [pinpai=" + pinpai + ", isStretch=" + isStretch + "]";
    }

}
           

測試類

package cn.xb;

import java.util.Date;

import cn.xb.entity.House;
import cn.xb.entity.Window;
/**
 * 測試類
 * @author binxu
 *
 */
public class MainApplication {
    public static void main(String[] args) {
        //new出來一棟房子
        House house = new House();
        //設定房子顔色、高度、擁有者姓名
        house.setColor("紅色");
        house.setHeight();
        house.setOwner("張三");
        house.setManufactureDate(new Date());
        Window window = new Window();
        window.setIsStretch(false);
        window.setPinpai("LOL牌");
        house.setWindow(window);
        System.out.println("原對象:" + house + ",精确時間為:" + house.getManufactureDate().getTime());

        try {
            House clone = (House) house.clone();
            System.out.println("克隆後的對象是否與原對象是在記憶體中是同一個:" + (clone == house));
            System.out.println("克隆後的對象" + clone + ",精确時間為:" + clone.getManufactureDate().getTime());
            System.out.println("克隆後的房屋的窗戶是否和原對象窗戶在記憶體中為同一個" + (clone.getWindow() == house.getWindow()));
            System.out.println("克隆後的房屋的窗戶是否和原對象生産日期在記憶體中為同一個" + (clone.getManufactureDate() == house.getManufactureDate()));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }

}
           

代碼輸出:

**原對象:House [color=紅色, height=8, owner=張三, window=Window [pinpai=LOL牌, isStretch=false], manufactureDate=Thu Aug 30 11:54:30 CST 2018],精确時間為:1535601270442 **克隆後的對象是否與原對象是在記憶體中是同一個:false **克隆後的對象House [color=紅色, height=8, owner=張三, window=Window [pinpai=LOL牌, isStretch=false], manufactureDate=Thu Aug 30 11:54:30 CST 2018],精确時間為:1535601270442 **克隆後的房屋的窗戶是否和原對象窗戶在記憶體中為同一個true **克隆後的房屋的窗戶是否和原對象生産日期在記憶體中為同一個true

通過輸出我們會發現雖然克隆後的房屋在記憶體中與原房屋在記憶體中已經不是同一塊,但是房屋類中的窗戶window引用和生産日期manufacture仍然為原房屋對象中的窗戶和日期引用,這樣我們去修改克隆後的對象或者原對象的窗戶屬性或者日期時,對應的另一個對象也會被影響,這樣暴露的問題是我們僅克隆了最外層的房屋,但是其内部的引用屬性仍然被原封不動的複制到克隆對象了

是以修改代碼讓window窗戶類也實作Cloneable接口,并且将房屋的clone方法改造下,不再是單一的調用JDK原封不動的clone方法,而是修改邏輯讓其内部的window和manufacture也被克隆一份到克隆對象中:

package cn.xb.entity;
/**
 * 窗戶
 * @author binxu
 *
 */
public class Window implements Cloneable {
    //品牌
    private String pinpai;
    //是否拉伸型
    private Boolean isStretch;
    public String getPinpai() {
        return pinpai;
    }
    public void setPinpai(String pinpai) {
        this.pinpai = pinpai;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    public Boolean getIsStretch() {
        return isStretch;
    }
    public void setIsStretch(Boolean isStretch) {
        this.isStretch = isStretch;
    }
    @Override
    public String toString() {
        return "Window [pinpai=" + pinpai + ", isStretch=" + isStretch + "]";
    }

}
package cn.xb.entity;

import java.util.Date;

/**
 * 房屋
 * @author binxu
 *
 */
public class House implements Cloneable {
    //房子顔色
    private String color;
    //房子高度
    private Integer height;
    //房子擁有着姓名
    private String owner;
    //窗戶
    private Window window;
    //建造日期
    private Date manufactureDate;

    public Date getManufactureDate() {
        return manufactureDate;
    }
    public void setManufactureDate(Date manufactureDate) {
        this.manufactureDate = manufactureDate;
    }
    public Window getWindow() {
        return window;
    }
    public void setWindow(Window window) {
        this.window = window;
    }
    public String getColor() {
        return color;
    }
    @Override
    public String toString() {
        return "House [color=" + color + ", height=" + height + ", owner=" + owner + ", window=" + window
                + ", manufactureDate=" + manufactureDate + "]";
    }
    public void setColor(String color) {
        this.color = color;
    }
    public Integer getHeight() {
        return height;
    }
    public void setHeight(Integer height) {
        this.height = height;
    }
    public String getOwner() {
        return owner;
    }
    public void setOwner(String owner) {
        this.owner = owner;
    }
    /**
     * 這裡要注意,Object類中的clone方法為protected修飾,我們想要在非本包調用就必須将protected修飾符改為public,這樣滿足重寫規範
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        //思路:先擷取到通過JDK原方法克隆得到的房屋克隆對象,然後将其帶過來的生産日期manufactureDate和窗戶window替換為對應的clone對象
        //這樣每一個引用都調用各自的clone方法,擷取到克隆對象,最後整合到最外層的house對象中去
        House clone = (House) super.clone();
        clone.setWindow((Window)this.window.clone());
        clone.setManufactureDate((Date)this.manufactureDate.clone());
        return clone;
    }

}
           

測試類

package cn.xb;

import java.util.Date;

import cn.xb.entity.House;
import cn.xb.entity.Window;
/**
 * 測試類
 * @author binxu
 *
 */
public class MainApplication {
    public static void main(String[] args) {
        //new出來一棟房子
        House house = new House();
        //設定房子顔色、高度、擁有者姓名
        house.setColor("紅色");
        house.setHeight();
        house.setOwner("張三");
        house.setManufactureDate(new Date());
        Window window = new Window();
        window.setIsStretch(false);
        window.setPinpai("LOL牌");
        house.setWindow(window);
        System.out.println("原對象:" + house + ",精确時間為:" + house.getManufactureDate().getTime());

        try {
            House clone = (House) house.clone();
            System.out.println("克隆後的對象是否與原對象是在記憶體中是同一個:" + (clone == house));
            System.out.println("克隆後的對象" + clone + ",精确時間為:" + clone.getManufactureDate().getTime());
            System.out.println("克隆後的房屋的窗戶是否和原對象窗戶在記憶體中為同一個" + (clone.getWindow() == house.getWindow()));
            System.out.println("克隆後的房屋的窗戶是否和原對象生産日期在記憶體中為同一個" + (clone.getManufactureDate() == house.getManufactureDate()));
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }

}
           

輸出:

原對象:House [color=紅色, height=8, owner=張三, window=Window [pinpai=LOL牌, isStretch=false], manufactureDate=Thu Aug 30 12:09:12 CST 2018],精确時間為:1535602152536 克隆後的對象是否與原對象是在記憶體中是同一個:false 克隆後的對象House [color=紅色, height=8, owner=張三, window=Window [pinpai=LOL牌, isStretch=false], manufactureDate=Thu Aug 30 12:09:12 CST 2018],精确時間為:1535602152536 克隆後的房屋的窗戶是否和原對象窗戶在記憶體中為同一個false 克隆後的房屋的窗戶是否和原對象生産日期在記憶體中為同一個false

這樣就解決了原對象的深度克隆的需求,但是這樣還沒有結束,我麼會發現一個問題,房屋類中有窗戶和日期兩個引用,那麼我們就要在clone中添加兩段代碼去分别擷取到窗戶和日期的clone對象,然後整合到房屋對象,那麼假如一個對象有1000個10000個引用屬性那?哈哈,有點誇張!或者一個類中的引用屬性不确定,需要經常變動,那麼每次變動就都要修改代碼!

是以這裡我們在推薦一種使用位元組流數組操作的方法,改變原來的思路,以一種拷貝檔案的形式來實作深度拷貝。不管你幾個引用屬性,我都一次搞定!

這裡我有一點想法,如有錯誤,非常感謝你的指正:對于計算機記憶體儲的任何東西,一個java檔案是你可以直接看到他字尾名為java的一個檔案,一個運作時的數組你看不到,但是我覺得對于計算機來說,這兩者都是一樣的,都是0和1,那麼我們以拷貝檔案的思路來拷貝對象,也就順理成章了,因為對計算機而言,都是拷貝0和1

修改以上房屋、窗戶、和測試類,上代碼:

package cn.xb.serentity;

import java.io.Serializable;
import java.util.Date;

/**
 * 房屋
 * @author binxu
 *
 */
public class House implements Serializable {
    //房子顔色
    private String color;
    //房子高度
    private Integer height;
    //房子擁有着姓名
    private String owner;
    //窗戶
    private Window window;
    //建造日期
    private Date manufactureDate;

    public Date getManufactureDate() {
        return manufactureDate;
    }
    public void setManufactureDate(Date manufactureDate) {
        this.manufactureDate = manufactureDate;
    }
    public Window getWindow() {
        return window;
    }
    public void setWindow(Window window) {
        this.window = window;
    }
    public String getColor() {
        return color;
    }
    @Override
    public String toString() {
        return "House [color=" + color + ", height=" + height + ", owner=" + owner + ", window=" + window
                + ", manufactureDate=" + manufactureDate + "]";
    }
    public void setColor(String color) {
        this.color = color;
    }
    public Integer getHeight() {
        return height;
    }
    public void setHeight(Integer height) {
        this.height = height;
    }
    public String getOwner() {
        return owner;
    }
    public void setOwner(String owner) {
        this.owner = owner;
    }

}
package cn.xb.serentity;

import java.io.Serializable;

/**
 * 窗戶
 * @author binxu
 *
 */
public class Window implements Serializable {
    //品牌
    private String pinpai;
    //是否拉伸型
    private Boolean isStretch;
    public String getPinpai() {
        return pinpai;
    }
    public void setPinpai(String pinpai) {
        this.pinpai = pinpai;
    }
    public Boolean getIsStretch() {
        return isStretch;
    }
    public void setIsStretch(Boolean isStretch) {
        this.isStretch = isStretch;
    }
    @Override
    public String toString() {
        return "Window [pinpai=" + pinpai + ", isStretch=" + isStretch + "]";
    }

}
           

一個重點!!既然是通過流的方式來拷貝,必須鎖要拷貝到類實作Serializable接口,否則報如下異常: java.io.NotSerializableException

上測試類:

package cn.xb;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;

import cn.xb.serentity.House;
import cn.xb.serentity.Window;

public class MainApplication1 {
    public Object copyObject(Object object) throws IOException, ClassNotFoundException {
        //建立位元組數組輸出流将索要拷貝對象寫入
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        //建立對象輸出流将位元組數組輸出流傳入直接将對象寫入位元組輸出流
        ObjectOutputStream objectOutputStrea = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStrea.writeObject(object);
        //将剛寫入的輸出流轉化為位元組數組傳入位元組數組輸入流
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        //對象輸入流包裝讀取為一個對象
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        return objectInputStream.readObject();
    }
    public static void main(String[] args) throws ClassNotFoundException, IOException {
        //new出來一棟房子
        House house = new House();
        //設定房子顔色、高度、擁有者姓名
        house.setColor("紅色");
        house.setHeight();
        house.setOwner("張三");
        house.setManufactureDate(new Date());
        Window window = new Window();
        window.setIsStretch(false);
        window.setPinpai("LOL牌");
        house.setWindow(window);
        System.out.println("原對象:" + house + ",精确時間為:" + house.getManufactureDate().getTime());
        //開始克隆
        MainApplication1 application1 = new MainApplication1();
        House clone = (House) application1.copyObject(house);
        System.out.println("克隆後的對象是否與原對象是在記憶體中是同一個:" + (clone == house));
        System.out.println("克隆後的對象" + clone + ",精确時間為:" + clone.getManufactureDate().getTime());
        System.out.println("克隆後的房屋的窗戶是否和原對象窗戶在記憶體中為同一個" + (clone.getWindow() == house.getWindow()));
        System.out.println("克隆後的房屋的窗戶是否和原對象生産日期在記憶體中為同一個" + (clone.getManufactureDate() == house.getManufactureDate()));
    }
}
           

輸出結果

原對象:House [color=紅色, height=8, owner=張三, window=Window [pinpai=LOL牌, isStretch=false], manufactureDate=Thu Aug 30 12:36:16 CST 2018],精确時間為:1535603776580 克隆後的對象是否與原對象是在記憶體中是同一個:false 克隆後的對象House [color=紅色, height=8, owner=張三, window=Window [pinpai=LOL牌, isStretch=false], manufactureDate=Thu Aug 30 12:36:16 CST 2018],精确時間為:1535603776580 克隆後的房屋的窗戶是否和原對象窗戶在記憶體中為同一個false 克隆後的房屋的窗戶是否和原對象生産日期在記憶體中為同一個false

這樣就完成了使用位元組數組流來實作深拷貝的過程。最後還是想說如有錯誤,非常感謝您的指正,您的指正可以讓我學到更多的東西!如果有知識點上的讨論,更是非常榮幸與開心!!