首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java clone() 深拷貝 vs 淺拷貝 vs 賦值引用:核心差異與正確姿勢

     編輯:Java 相關知識

三種「複製」方式本質對比

方式代碼內存行為修改副本影響原對象?
賦值引用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
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved