三種「複製」方式本質對比
| 方式 | 代碼 | 內存行為 | 修改副本影響原對象? |
| 賦值引用 | Student s2 = s1; | 棧上複製引用指向同一堆對象 | 是 (完全同一對象) |
| 淺拷貝 | Student s2 = s1.clone(); (默認實現) | 新建對象,基本類型複製值,引用類型複製引用 | 引用類型字段是 |
| 深拷貝 | Student s2 = deepClone(s1); (需自定義實現) | 新建對象,遞歸複製所有引用類型字段所指對象 | 否 (完全獨立) |
代碼驗證:淺拷貝的陷阱
// 1. 實現 Cloneable (標記接口)
class Address implements Cloneable {
String city;
// getter/setter/toString 省略
}
class Student implements Cloneable {
String name; // 基本類型/不可變對象
int age;
Address address; // 引用類型:淺拷貝只複製引用!
@Override
protected Student clone() throws CloneNotSupportedException {
return (Student) super.clone(); // 默認淺拷貝
}
}
// 2. 測試
public class CloneDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Student s1 = new Student();
s1.setName("張三");
s1.setAge(20);
s1.setAddress(new Address("北京"));
Student s2 = s1.clone(); // 淺拷貝
Student s3 = s1; // 賦值引用
System.out.println("=== 修改前 ===");
System.out.println("s1: " + s1);
System.out.println("s2: " + s2);
System.out.println("s3: " + s3);
// 修改基本類型/不可變字段
s2.setName("李四");
s2.setAge(25);
// 修改引用類型字段的內部狀態
s2.getAddress().setCity("上海"); // 影響 s1!
System.out.println("\n=== 修改 s2 後 ===");
System.out.println("s1: " + s1); // name=張三, age=20, address=上海 ← 被改變!
System.out.println("s2: " + s2); // name=李四, age=25, address=上海
System.out.println("s3: " + s3); // name=張三, age=20, address=上海 ← 同一對象
// 賦值引用修改
s3.setName("王五");
System.out.println("\n=== 修改 s3 後 ===");
System.out.println("s1: " + s1); // name=王五 ← 同一對象!
}
}
深拷貝正確實現三種方案
方案一:重寫 clone() 手動深拷貝 (推薦簡單對象)
class Student implements Cloneable {
String name;
int age;
Address address;
List hobbies;
@Override
protected Student clone() throws CloneNotSupportedException {
Student cloned = (Student) super.clone(); // 淺拷貝基礎
// 手動深拷貝引用字段
if (address != null) {
cloned.address = address.clone(); // Address 也需實現 Cloneable
}
if (hobbies != null) {
cloned.hobbies = new ArrayList<>(hobbies); // 集合深拷貝
}
return cloned;
}
}
class Address implements Cloneable {
String city;
@Override protected Address clone() { ... }
}
方案二:序列化反序列化 (通用、處理複雜對象圖)
// 工具類
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);
}
}
}
// 使用:所有類必須實現 Serializable
Student s2 = DeepCloneUtil.deepClone(s1);
方案三:JSON 轉換 (無需實現接口、性能一般)
// 依賴 Jackson/Gson
public class JsonCloneUtil {
private static final ObjectMapper MAPPER = new ObjectMapper();
public static <T> T deepClone(T obj, Class<T> clazz) {
try {
String json = MAPPER.writeValueAsString(obj);
return MAPPER.readValue(json, clazz);
} catch (JsonProcessingException e) {
throw new RuntimeException("JSON 深拷貝失敗", e);
}
}
}
// 使用
Student s2 = JsonCloneUtil.deepClone(s1, Student.class);
方案對比與選型建議
| 維度 | 手動 clone | 序列化 | JSON |
| 性能 | 最高 | 中 (IO 開銷) | 較低 (解析開銷) |
| 適用場景 | 字段固定、可控制源碼 | 複雜對象圖、第三方類 | 跨語言、調試、簡單對象 |
| 要求 | 實現 Cloneable、手動處理引用 | 實現 Serializable、無 transient 字段 | 無特殊要求、需無循環引用 |
| 處理循環引用 | 手動避免 | 自動處理 | 需配置 |
現代最佳實踐:不可變對象 + 工廠複製
// 使用 Record (Java 14+) 或 Lombok @Value 實現不可變對象
record Student(String name, int age, Address address) {
// 自動生成構造器、getter、equals、hashCode、toString
// with 方法 (Java 16+) 支持單字段修改返回新對象
public Student withName(String name) { return new Student(name, age, address); }
public Student withAge(int age) { return new Student(name, age, address); }
public Student withAddress(Address address) { return new Student(name, age, address); }
}
record Address(String city) {}
// 使用:無需 clone,直接構建新對象
Student s1 = new Student("張三", 20, new Address("北京"));
Student s2 = s1.withName("李四"); // 新對象,s1 不變
Student s3 = s1.withAddress(new Address("上海")); // 新對象,s1 不變
總結
- 賦值引用:同一對象,修改相互影響
- 淺拷貝 (clone 默認):新對象,但引用字段共享,修改引用字段內部狀態相互影響
- 深拷貝:完全獨立副本,修改互不影響
- 首選不可變對象設計:從根源消除拷貝需求,Record/Value Object 是現代 Java 最佳實踐
- 必須拷貝時:簡單可控對象用手動 clone,複雜/第三方對象用序列化/JSON