Java 物件拷貝分為淺拷貝與深拷貝。實作 Cloneable 介面並覆寫 clone() 雖能實現淺拷貝,但參考型別欄位僅複製參考位址,導致多個物件共用同一個子物件,修改時會互相影響。本文透過實例說明淺拷貝缺陷,並展示序列化實現通用深拷貝的優雅寫法。
public class Person implements Cloneable {
private String name;
private Email email; // 參考型別
// getter/setter/建構子略
@Override
protected Person clone() {
try {
return (Person) super.clone(); // 預設淺拷貝
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
public class Email {
private String subject, content;
// 建構子、getter/setter 略
}
測試程式:
Email email = new Email("會議通知", "12:30 開會");
Person p1 = new Person("張三", email);
Person p2 = p1.clone();
p2.setName("李四");
p1.getEmail().setContent("12:00 開會"); // 修改張三的郵件內容
System.out.println(p2.getEmail().getContent()); // 輸出「12:00 開會」——李四的郵件也被改了!
Email 實例。@Override
protected Person clone() {
Person p = (Person) super.clone();
p.setEmail(new Email(this.email.getSubject(), this.email.getContent())); // 手動 new
return p;
}
缺點:每個類別都要寫 clone()、屬性一多極其繁瑣、維護困難、違反 DRY 原則。
import java.io.*;
public class DeepCloneUtil {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T deepClone(T obj) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(obj);
try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis)) {
return (T) ois.readObject();
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("深拷貝失敗", e);
}
}
}
Person p1 = new Person("張三", new Email("會議", "12:30"));
Person p2 = DeepCloneUtil.deepClone(p1);
p2.setName("李四");
p1.getEmail().setContent("12:00"); // 只影響 p1
System.out.println(p2.getEmail().getContent()); // 仍為 12:30
Serializable 介面。transient 修飾的欄位不會被序列化,深拷貝後為預設值(null/0)。淺拷貝僅適用於純基本型別或不可變物件組成的類別。若物件圖包含可變參考型別,請採用序列化深拷貝或第三方庫(如 Apache Commons Lang SerializationUtils.clone()),一次解決所有層級拷貝問題。