在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等等)