在Java中,如何實(shí)現(xiàn)對(duì)象的拷貝?
在Java中,對(duì)象的拷貝可以分為淺拷貝(shallow copy)和深拷貝(deep copy)。
- 「淺拷貝」:
- 創(chuàng)建一個(gè)新對(duì)象,然后將原始對(duì)象中的非靜態(tài)字段復(fù)制到新對(duì)象,如果字段是值類型,那么對(duì)該字段執(zhí)行逐位復(fù)制。如果字段是引用類型,則復(fù)制引用但不復(fù)制引用的對(duì)象。因此,原始對(duì)象及其副本引用同一個(gè)對(duì)象。
- 在Java中,我們可以通過實(shí)現(xiàn)Cloneable接口并重寫clone()方法來實(shí)現(xiàn)淺拷貝。需要注意的是,Object類中的clone()方法是受保護(hù)的,所以我們需要在我們的類中將其重寫為public。
- 另外,如果對(duì)象的字段也是需要拷貝的復(fù)雜對(duì)象,那么可能需要在這些類中也實(shí)現(xiàn)Cloneable接口并重寫clone()方法。
- 「深拷貝」:
創(chuàng)建一個(gè)新對(duì)象,然后將原始對(duì)象中的非靜態(tài)字段復(fù)制到新對(duì)象。如果字段是值類型,那么對(duì)該字段執(zhí)行逐位復(fù)制。如果字段是引用類型,則遞歸地復(fù)制該字段引用的對(duì)象,而不是只復(fù)制引用。
在Java中,深拷貝通常需要我們自己寫代碼來實(shí)現(xiàn),因?yàn)镴ava并沒有提供直接實(shí)現(xiàn)深拷貝的內(nèi)置方法。
深拷貝的一個(gè)常見實(shí)現(xiàn)方式是使用序列化。我們可以將對(duì)象寫入到一個(gè)流中,然后再?gòu)牧髦凶x取出來,這樣得到的就是原對(duì)象的一個(gè)深拷貝。但是這種方法有一些限制,比如被拷貝的對(duì)象以及它引用的所有對(duì)象都必須是可序列化的。
注意:Cloneable接口和clone()方法的設(shè)計(jì)在Java社區(qū)中常常被認(rèn)為是有缺陷的,因?yàn)樗鼈冇泻芏鄦栴},比如Cloneable接口沒有定義任何方法(它是一個(gè)標(biāo)記接口),clone()方法的訪問修飾符是protected,而且它使用的是淺拷貝,這可能會(huì)導(dǎo)致意外的對(duì)象共享問題。因此,在實(shí)際編程中,很多開發(fā)者更傾向于自己寫代碼來實(shí)現(xiàn)對(duì)象的拷貝,而不是使用Cloneable接口和clone()方法。
- 通過實(shí)現(xiàn)Cloneable接口并重寫clone()方法來實(shí)現(xiàn)淺拷貝
我們定一個(gè)實(shí)體類People,實(shí)現(xiàn)了Cloneable接口,并且重寫了clone()方法當(dāng)然也是直接調(diào)用的父類的clone()方法。
public class People implements Cloneable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected People clone() throws CloneNotSupportedException {
return (People) super.clone();
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
測(cè)試拷貝:
public class Main {
public static void main(String[] args) {
People people = new People();
people.setName("Reathin");
people.setAge(30);
System.out.println("原People" + people.toString());
try {
People people1 = people.clone();
System.out.println("拷貝People" + people1);
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
輸出結(jié)果如下,我們實(shí)現(xiàn)了一次淺拷貝。
圖片
- 通過將原始對(duì)象中的非靜態(tài)字段復(fù)制到新對(duì)象實(shí)現(xiàn)深拷貝
People people2 = new People();
people2.setName(people.getName());
people2.setAge(people.getAge());
System.out.println("深拷貝對(duì)象1" + people2);
- 通過序列化對(duì)象流實(shí)現(xiàn)深拷貝
// 序列化對(duì)象到字節(jié)數(shù)組
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(people);
byte[] serializedData = byteArrayOutputStream.toByteArray();
// 從字節(jié)數(shù)組中反序列化對(duì)象以創(chuàng)建深拷貝
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serializedData);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
People people3 = (People) objectInputStream.readObject();
System.out.println("深拷貝對(duì)象2" + people3);
最終輸出結(jié)果如下:
圖片
完整示例代碼:
public class People implements Cloneable, Serializable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected People clone() throws CloneNotSupportedException {
return (People) super.clone();
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException, CloneNotSupportedException {
People people = new People();
people.setName("Reathin");
people.setAge(30);
System.out.println("原People" + people.toString());
People people1 = people.clone();
System.out.println("淺拷貝People" + people1);
//深拷貝方式一
People people2 = new People();
people2.setName(people.getName());
people2.setAge(people.getAge());
System.out.println("深拷貝對(duì)象1" + people2);
//深拷貝方式2
// 序列化對(duì)象到字節(jié)數(shù)組
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(people);
byte[] serializedData = byteArrayOutputStream.toByteArray();
// 從字節(jié)數(shù)組中反序列化對(duì)象以創(chuàng)建深拷貝
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serializedData);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
People people3 = (People) objectInputStream.readObject();
System.out.println("深拷貝對(duì)象2" + people3);
}
}
開發(fā)中可以使用第三方庫(kù)如Apache Commons Lang的SerializationUtils類或Google的Guava庫(kù)來實(shí)現(xiàn)對(duì)象的深拷貝。這些庫(kù)提供了更加靈活和方便的深拷貝實(shí)現(xiàn)方式,同時(shí)也提供了更多的自定義選項(xiàng)和錯(cuò)誤處理機(jī)制。