Java clone() 複製

clone() 方法用於建立物件的副本。物件必須實作 Cloneable 介面才能被複製。

基本用法

public class Person implements Cloneable {
    private String name;
    private int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    protected Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
}

// 使用
Person original = new Person("Alice", 25);
Person copy = original.clone();

copy.setName("Bob");
System.out.println(original.getName());  // Alice(不受影響)
System.out.println(copy.getName());      // Bob

淺複製 vs 深複製

淺複製(Shallow Copy)

public class Person implements Cloneable {
    private String name;
    private Address address;  // 參考類型
    
    @Override
    protected Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();  // 淺複製
    }
}

Person original = new Person("Alice", new Address("台北"));
Person copy = original.clone();

copy.getAddress().setCity("高雄");
System.out.println(original.getAddress().getCity());  // 高雄!(被影響了)

淺複製只複製基本類型和參考,參考類型仍指向同一物件。

深複製(Deep Copy)

public class Address implements Cloneable {
    private String city;
    
    @Override
    protected Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }
}

public class Person implements Cloneable {
    private String name;
    private Address address;
    
    @Override
    protected Person clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone();
        cloned.address = this.address.clone();  // 深複製參考物件
        return cloned;
    }
}

Person copy = original.clone();
copy.getAddress().setCity("高雄");
System.out.println(original.getAddress().getCity());  // 台北(不受影響)

使用複製建構子

public class Person {
    private String name;
    private Address address;
    
    // 複製建構子
    public Person(Person other) {
        this.name = other.name;
        this.address = new Address(other.address);
    }
}

public class Address {
    private String city;
    
    public Address(Address other) {
        this.city = other.city;
    }
}

// 使用
Person original = new Person("Alice", new Address("台北"));
Person copy = new Person(original);  // 深複製

使用序列化深複製

public static <T extends Serializable> T deepCopy(T obj) {
    try {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        
        @SuppressWarnings("unchecked")
        T copy = (T) ois.readObject();
        return copy;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

// 使用(所有相關類別需實作 Serializable)
Person copy = deepCopy(original);

使用 Record

Record 是不可變的,通常不需要複製,但可以建立新實例:

public record Person(String name, Address address) {
    public Person withName(String newName) {
        return new Person(newName, this.address);
    }
    
    public Person withAddress(Address newAddress) {
        return new Person(this.name, newAddress);
    }
}

陣列複製

// 一維陣列
int[] original = {1, 2, 3, 4, 5};
int[] copy1 = original.clone();
int[] copy2 = Arrays.copyOf(original, original.length);
int[] copy3 = new int[original.length];
System.arraycopy(original, 0, copy3, 0, original.length);

// 二維陣列(淺複製)
int[][] arr2d = {{1, 2}, {3, 4}};
int[][] shallowCopy = arr2d.clone();

// 二維陣列(深複製)
int[][] deepCopy = new int[arr2d.length][];
for (int i = 0; i < arr2d.length; i++) {
    deepCopy[i] = arr2d[i].clone();
}

集合複製

// List 淺複製
List<String> original = new ArrayList<>(Arrays.asList("A", "B", "C"));
List<String> copy1 = new ArrayList<>(original);
List<String> copy2 = List.copyOf(original);  // 不可變

// 深複製(需要手動複製元素)
List<Person> people = new ArrayList<>();
List<Person> deepCopy = people.stream()
    .map(p -> new Person(p))  // 使用複製建構子
    .collect(Collectors.toList());

clone() 的問題

  1. 需要實作 Cloneable:否則拋出 CloneNotSupportedException
  2. 淺複製問題:預設只做淺複製
  3. 建構子不被呼叫clone() 不呼叫建構子
  4. final 欄位問題:無法在 clone 中設定 final 欄位

替代方案比較

方法優點缺點
clone()內建機制複雜、容易出錯
複製建構子清晰明確需手動實作
序列化自動深複製效能較差
Builder靈活較多程式碼

重點整理

  • 實作 Cloneable 介面才能使用 clone()
  • 預設是淺複製,參考類型需要手動深複製
  • 建議使用複製建構子取代 clone()
  • 序列化可以簡單實現深複製(但效能較差)
  • 不可變物件不需要複製
  • 陣列可用 clone()Arrays.copyOf()