title: java深淺拷貝
date: 2018-12-16 15:54:20
updated: 2020-03-15 13:29:14
categories: java
tags:
- java
此文檔爲java深淺拷貝的學習總結
Java 淺拷貝和深拷貝
來源:Java 淺拷貝和深拷貝
1.直接賦值
A1 a1 = a2;
那麼對象a1/a2指向同一個對象,即a2對對象的操作,a1也跟着變了
2.淺拷貝
類的方法中加個clone方法。這種淺拷貝實現的是:創建一個新對象,然後將當前對象的非靜態字段複製到該新對象,如果字段是值類型的,那麼對該字段執行復制;如果該字段是引用類型的話,則複製引用但不復制引用的對象。因此,原始對象及其副本引用同一個對象。
private String name; //姓名
private String sex; //性別
private int age; //年齡
private String experience; //工作經歷
public Object clone() {
try {
return (Resume)super.clone();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
Resume zhangsan = new Resume("zhangsan","男",24);
zhangsan.setExperience("2009-2013就讀於家裏蹲大學,精通JAVA,C,C++,C#等代碼拷貝和粘貼");
zhangsan.displayResume();
Resume zhangsan1 = (Resume)zhangsan.clone();
zhangsan1.setAge(23);
zhangsan1.displayResume();
Resume zhangsan2 = (Resume)zhangsan.clone();
zhangsan2.setExperience("2009-2013就讀於家裏蹲大學,精通JAVA,C,C++,C#等代碼");
zhangsan2.displayResume();
zhangsan.displayResume();
}
上面代碼沒問題,因爲數據沒有引用類型,a1/a2指向對象不同,彼此對立,但是下面的就不行了:
class Experience {
private String educationBackground;
private String skills;
public void setExperience(String educationBackground, String skills) {
// TODO Auto-generated constructor stub
this.educationBackground = educationBackground;
this.skills = skills;
}
public String toString() {
return educationBackground + skills;
}
}
/* 建立類,實現Clone方法 */
class Resume implements Cloneable{
private String name; //姓名
private String sex; //性別
private int age; //年齡
private Experience experience; //工作經歷,引用類型
public Resume(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
this.experience = new Experience();
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public Experience getExperience() {
return experience;
}
public void setExperience(String educationBackground, String skills) {
experience.setExperience(educationBackground, skills);
}
public void displayResume() {
System.out.println("姓名:"+name+" 性別:"+sex+" 年齡:"+age);
System.out.println("工作經歷:"+experience.toString());
}
public Object clone() {
try {
return (Resume)super.clone();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
public class MainClass {
public static void main(String[] args) {
Resume zhangsan = new Resume("zhangsan","男",24);
zhangsan.setExperience("2009-2013就讀於家裏蹲大學","精通JAVA,C,C++,C#等代碼拷貝和粘貼");
zhangsan.displayResume();
Resume zhangsan2 = (Resume)zhangsan.clone();
zhangsan2.setExperience("2009-2013就讀於家裏蹲大學","精通JAVA,C,C++,C#等");
zhangsan2.displayResume();
zhangsan.displayResume();
zhangsan2.displayResume();
}
}
out:
姓名:zhangsan 性別:男 年齡:24
工作經歷:2009-2013就讀於家裏蹲大學精通JAVA,C,C++,C#等代碼拷貝和粘貼
姓名:zhangsan 性別:男 年齡:24
工作經歷:2009-2013就讀於家裏蹲大學精通JAVA,C,C++,C#等
姓名:zhangsan 性別:男 年齡:24
工作經歷:2009-2013就讀於家裏蹲大學精通JAVA,C,C++,C#等
姓名:zhangsan 性別:男 年齡:24
工作經歷:2009-2013就讀於家裏蹲大學精通JAVA,C,C++,C#等
如果該字段是引用類型的話,則複製引用但不復制引用的對象。因此,原始對象及其副本引用同一個對象。”其實也就是說,zhangsan和zhangsan2裏面的Experience類指向的是同一個對象,不管是zhangsan裏面的Experience變化,還是zhangsan2裏面的Experience變化都會影響另外一個。
3.深拷貝
java深拷貝兩種方法:
- 1.繼承Cloneable重寫clone方法
- 2.對象實現Serializable通過序列化實現深拷貝
其實出現問題的關鍵就在於clone()方法上,我們知道該clone()方法是使用Object類的clone()方法,但是該方法存在一個缺陷,它並不會將對象的所有屬性全部拷貝過來,而是有選擇性的拷貝,基本規則如下:
1、 基本類型
如果變量是基本很類型,則拷貝其值,比如int、float等。
2、 String字符串
若變量爲String字符串,則拷貝其地址引用。但是在修改時,它會從字符串池中重新生成一個新的字符串,原有對象保持不變。
3、 對象/引用類型
如果變量是一個實例對象,則拷貝其地址引用,也就是說此時新對象與原來對象是公用該實例變量。(即淺拷貝)
因此深拷貝就是在clone方法中把引用類型深拷貝即可。
public class YuelyLog implements Cloneable {
private Attachment attachment;//引用類型
private String name;
private String date;
@Override
protected YuelyLog clone() throws CloneNotSupportedException {
YuelyLog o = (YuelyLog)super.clone(); //Object.clone()
o.attachment = (Attachment)attachment.clone();
return o;
}
}
三點說明:
-
1、clone方法實現要點:實現 Cloneable接口 + 使用protected訪問修飾符重新定義clone方法以實現深拷貝
-
2、clone方法是Object類的一個protected方法,因此無法調用anObj.clone(),也就是說用戶在編寫的代碼中不能直接調用它,只有Employee類才能克隆Employee對象(這樣通過類來拷貝將避免出現淺拷貝的 問題)
-
3、Cloneable接口是java提供的幾個標記接口(tagging interface),與通常接口不同的是它沒有指定方法,Cloneable中的clone方法是從Object類繼承而來,這個Cloneable接口作爲標記是想說明如果一個對象需要克隆而沒有實現Cloneable接口,就會產生一個異常。
深拷貝問題解決了,但對於上面的解決方案還是存在一個問題,若我們系統中存在大量的對象是通過拷貝生成的,如果我們每一個類都寫一個clone()方法,並將還需要進行深拷貝,新建大量的對象,這個工程是非常大的,這裏我們可以利用對象的序列化來實現對象的拷貝。
如何利用序列化來完成對象的拷貝呢?在內存中通過字節流的拷貝是比較容易實現的。把母對象寫入到一個字節流中,再從字節流中將其讀出來,這樣就可以創建一個新的對象了,並且該新對象與母對象之間並不存在引用共享的問題,真正實現對象的深拷貝。
public class CloneUtils {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj){
T cloneObj = null;
try {
//寫入字節流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
//分配內存,寫入原始對象,生成新對象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新對象
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
}
使用該工具類的對象必須要實現Serializable接口,否則是沒有辦法實現克隆的。
public class Person implements Serializable{
private static final long serialVersionUID = 2631590509760908280L;
..................
//去除clone()方法
}
public class Email implements Serializable{
private static final long serialVersionUID = 1267293988171991494L;
....................
}
使用該工具類的對象只要實現Serializable接口就可實現對象的克隆,無須實現Cloneable接口中的clone()方法。
public class Client {
public static void main(String[] args) {
//寫封郵件
Email email = new Email("請參加會議","請與今天12:30到二會議室參加會議...");
Person person1 = new Person("張三",email);
Person person2 = CloneUtils.clone(person1);
person2.setName("李四");
Person person3 = CloneUtils.clone(person1);
person3.setName("王五");
person1.getEmail().setContent("請與今天12:00到二會議室參加會議...");
System.out.println(person1.getName() + "的郵件內容是:" + person1.getEmail().getContent());
System.out.println(person2.getName() + "的郵件內容是:" + person2.getEmail().getContent());
System.out.println(person3.getName() + "的郵件內容是:" + person3.getEmail().getContent());
}
}
-------------------
Output:
張三的郵件內容是:請與今天12:00到二會議室參加會議...
李四的郵件內容是:請與今天12:30到二會議室參加會議...
王五的郵件內容是:請與今天12:30到二會議室參加會議...