【Java】淺拷貝和深拷貝簡述

前言

  在Java中關於對象的拷貝咱們大體能夠分爲兩種,一種是淺拷貝(也叫引用拷貝),另一種是深拷貝(也稱值拷貝)。java

示例

  我相信絕大多數程序員Ctrl+C、Ctrl+V都玩的很溜,我也同樣哈。工做週報我以爲你們在熟悉不過了吧。以我自身寫週報爲例子,爲了節省本身的時間(主要仍是本身懶),我基本都是Ctrl+C、Ctrl+V別人寫好的週報格式進行現改。一人一份工做週報,總不能有兩我的的週報一字不差、如出一轍的吧。或多或少仍是有點出入的,好比改下標題,發件人,工做內容等等。程序員

淺拷貝

  定義:被複制的對象全部的變量都含有與原來對象相同的值,全部的對其餘對象的引用都仍然指向原來的對象(即原始的對象和其副本引用同一個對象)。
  
  先來看下程序代碼數組

/**
 * 工做週報類
 * @author zhh
 * @date 2017-08-23 上午11:03:47
 */
class Report implements Cloneable {
    
    private String title;        // 標題
    private String sender;        // 發送者
    private String receiver;    // 接收者
    private ArrayList<String> content; //內容
    
    public Report(String title, String sender, String receiver) {
        this.title = title;
        this.sender = sender;
        this.receiver = receiver;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getSender() {
        return sender;
    }
    public void setSender(String sender) {
        this.sender = sender;
    }
    public String getReceiver() {
        return receiver;
    }
    public void setReceiver(String receiver) {
        this.receiver = receiver;
    }
    public ArrayList<String> getContent() {
        return content;
    }
    public void setContent(ArrayList<String> content) {
        this.content = content;
    }
    
    public void print() {
        System.out.println(this);
    }
    
    @Override
    public Report clone() {
        Report msg = null;
        try {
            msg = (Report) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return msg;
    }
    
    @Override
    public String toString() {
        return "Report [title=" + title + ", sender=" + sender + ", receiver=" + receiver + ", content=" + content
                + "]";
    }
    
}

  舉個例子,我這裏拷貝下公司UI小姐姐的週報模板現改現賣,像標題,發送者,內容什麼的大體仍是得改改。寫個主方法測試下。框架

public static void main(String[] args) {
        Report report = new Report("yg工做週報", "yg", "boss");
        ArrayList<String> content = new ArrayList<>();
        content.add("1.參加研討會議, 肯定需求");
        content.add("2.設計首頁的icon以及界面");
        report.setContent(content);
        
        Report report1 = report.clone();
        report1.setTitle("zhh工做週報");
        report1.setSender("zhh");
        ArrayList<String> content1 = report1.getContent();
        content1.set(1, "2.搭建基礎的後臺框架");
        report1.setContent(content1);
        
        System.out.println("----------UI小姐姐工做週報--------------");
        report.print();
        System.out.println("----------個人工做週報--------------");
        report1.print();
        
    }

  程序運行的結果以下:ide

----------UI小姐姐工做週報--------------
Report [title=yg工做週報, sender=yg, receiver=boss, content=[1.參加研討會議, 肯定需求, 2.搭建基礎的後臺框架]]
----------個人工做週報--------------
Report [title=zhh工做週報, sender=zhh, receiver=boss, content=[1.參加研討會議, 肯定需求, 2.搭建基礎的後臺框架]]

  雖然執行沒有發生什麼異常,但其結果顯然是不對的。UI小姐姐跟我乾的活同樣啦?老闆看了顯然也是一臉懵逼的。搞很差我也就被UI小姐姐給頂替掉了(開個玩笑哈)。測試

  但爲何會這樣呢?克隆之後爲何個人工做內容把UI的工做內容替換了呢?而其餘標題、發送者卻沒有這種狀況。
  咱們知道在Java當中Object類是全部類的頂級父類,而其clone方法只會拷貝對象中的基本數據類型,對於數組、容器對象、引用對象等都不會拷貝。程序Report類自寫的clone方法中 msg = (Report) super.clone(); 就是調用了Object類的clone方法。String類雖然也是引用類型,但因爲其的特殊性(final 類),雖然複製的引用,可是修改值的時候並無改變被複制對象的值;而ArrayList複製的僅僅是引用,致使本來引用和副本引用指向同一對象,因此上述代碼修改任意一個對象的content都會影響另一個。this

深拷貝

  根據上面的分析,淺拷貝是沒法去完成含有除基本數據類型以外對象的拷貝的。
  定義:被複制的對象全部的變量都含有與原來對象相同的值,全部的對其餘對象的引用也都指向複製過的新的對象(即原始的對象和其副本引用不一樣對象)。
  
  這裏咱們本身實現深拷貝,讓原始對象和其副本對象指向不一樣對象。
  其實代碼和淺拷貝大體相同,差異只是在Report類重寫的clone方法,這裏我就單獨拿出來寫下了。設計

...

    @Override
    public Report clone() {
        Report msg = null;
        try {
            msg = (Report) super.clone();
            // 將引用對象 content 也 clone下
            msg.content = (ArrayList<String>) this.content.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return msg;
    }
...

  測試的主方法同淺拷貝,不作修改直接運行,運行的結果以下:code

----------UI小姐姐工做週報--------------
Report [title=yg工做週報, sender=yg, receiver=boss, content=[1.參加研討會議, 肯定需求, 2.設計首頁的icon以及界面]]
----------個人工做週報--------------
Report [title=zhh工做週報, sender=zhh, receiver=boss, content=[1.參加研討會議, 肯定需求, 2.搭建基礎的後臺框架]]

  這裏你能夠看到,拷貝後二者的內容之間並無相互的影響。對象

擴展

  深拷貝除了上述實現方式外,也能夠用序列化來實現。

/**
 * 序列化實現深拷貝
 * @author zhh
 * @date 2017-08-23 下午1:04:44
 */
class Report implements Serializable {
    
    private static final long serialVersionUID = -760030405417987698L;
    
    private String title;        // 標題
    private String sender;        // 發送者
    private String receiver;    // 接收者
    private ArrayList<String> content; //內容
    
    public Report(String title, String sender, String receiver) {
        this.title = title;
        this.sender = sender;
        this.receiver = receiver;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getSender() {
        return sender;
    }
    public void setSender(String sender) {
        this.sender = sender;
    }
    public String getReceiver() {
        return receiver;
    }
    public void setReceiver(String receiver) {
        this.receiver = receiver;
    }
    public ArrayList<String> getContent() {
        return content;
    }
    public void setContent(ArrayList<String> content) {
        this.content = content;
    }
    
    public void print() {
        System.out.println(this);
    }
    
    @Override
    public Report clone() {
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;
        try {
            // 1.將對象序列化成流
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            
            // 2.將流序列化成對象
            bis = new ByteArrayInputStream(bos.toByteArray());;
            ois = new ObjectInputStream(bis);
            return (Report) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        
    }
    
    @Override
    public String toString() {
        return "Report [title=" + title + ", sender=" + sender + ", receiver=" + receiver + ", content=" + content
                + "]";
    }
    
}

  測試的主方法同淺拷貝,不作修改直接運行,運行的結果以下:

----------UI小姐姐工做週報--------------
Report [title=yg工做週報, sender=yg, receiver=boss, content=[1.參加研討會議, 肯定需求, 2.設計首頁的icon以及界面]]
----------個人工做週報--------------
Report [title=zhh工做週報, sender=zhh, receiver=boss, content=[1.參加研討會議, 肯定需求, 2.搭建基礎的後臺框架]]

  事實證實,用序列化來實現對象的深拷貝也是可行的。主要緣由是在對象序列化流的過程中,寫在流裏面的是對象的一個拷貝,而本來的對象仍然存在堆內。

序列化實現深拷貝過程當中,咱們實現了Serializable這個空接口,來標明Report類可序列化。
這裏要說一下 爲何要給 serialVersionUID 賦值
舉個例子,當對象序列化存到硬盤上後,比方我修改了這個對象的屬性,那麼在反序列化的過程就會出現異常。
一旦咱們給 serialVersionUID 賦值,當序列化和反序列化的 serialVersionUID 相同的時候,中間過程修改對象屬性就不會像上面拋出異常,而是以屬性的對應類型賦默認值(如String類型默認爲null,int類型默認爲0等等)
相關文章
相關標籤/搜索