杰瑞科技汇

Java List深拷贝与浅拷贝如何实现?

Of course! In Java, "cloning" a list can mean several different things, depending on your exact needs. It's crucial to understand the difference between a shallow copy and a deep copy.

Java List深拷贝与浅拷贝如何实现?-图1
(图片来源网络,侵删)

Let's break down the most common ways to clone a List in Java.


Shallow Copy (Most Common)

A shallow copy creates a new list object, but the elements inside the new list are the same references as the elements in the original list.

  • When to use it: When the elements in your list are immutable (like String, Integer, Double, or any custom class where the state cannot be changed after creation).
  • When NOT to use it: If your list contains mutable objects (like StringBuilder, ArrayList, or your own custom classes with setters), changes made to an object in the original list will be reflected in the copied list, and vice-versa.

Method 1: The Constructor (Simplest & Most Recommended)

This is the most straightforward and idiomatic way to create a shallow copy.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ListCloneExample {
    public static void main(String[] args) {
        // Original list of immutable String objects
        List<String> originalList = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));
        // Create a shallow copy using the ArrayList constructor
        List<String> shallowCopy = new ArrayList<>(originalList);
        System.out.println("Original List: " + originalList);
        System.out.println("Shallow Copy:  " + shallowCopy);
        // Modify the original list
        originalList.add("Date");
        System.out.println("\nAfter adding 'Date' to original:");
        System.out.println("Original List: " + originalList);
        System.out.println("Shallow Copy:  " + shallowCopy); // Copy is unchanged
        // Modify an element in the original list (Strings are immutable, so this creates a new object)
        originalList.set(0, "Apricot");
        System.out.println("\nAfter setting index 0 to 'Apricot' in original:");
        System.out.println("Original List: " + originalList);
        System.out.println("Shallow Copy:  " + shallowCopy); // Copy is unchanged
        // --- Example with MUTABLE objects ---
        List<List<String>> nestedList = new ArrayList<>();
        nestedList.add(new ArrayList<>(Arrays.asList("X", "Y")));
        List<List<String>> shallowCopyNested = new ArrayList<>(nestedList);
        System.out.println("\n--- Nested List Example ---");
        System.out.println("Original Nested: " + nestedList);
        System.out.println("Shallow Copy Nested: " + shallowCopyNested);
        // Modify the inner list in the original
        nestedList.get(0).add("Z");
        System.out.println("\nAfter modifying the inner list in the original:");
        System.out.println("Original Nested: " + nestedList); // Now contains [X, Y, Z]
        System.out.println("Shallow Copy Nested: " + shallowCopyNested); // ALSO contains [X, Y, Z]!
    }
}

Method 2: addAll()

This achieves the same result as the constructor method. It's slightly more verbose but equally valid.

Java List深拷贝与浅拷贝如何实现?-图2
(图片来源网络,侵删)
List<String> originalList = new ArrayList<>(Arrays.asList("Apple", "Banana"));
List<String> shallowCopy = new ArrayList<>();
shallowCopy.addAll(originalList);

Method 3: Java 8 Streams (Collectors.toList())

This is a modern, functional approach. It's concise and works well for read-only lists.

import java.util.List;
import java.util.stream.Collectors;
List<String> originalList = new ArrayList<>(Arrays.asList("Apple", "Banana"));
List<String> shallowCopy = originalList.stream()
                                       .collect(Collectors.toList());

Deep Copy

A deep copy creates a new list object and new copies of all the elements inside it. Changes to the original list or its elements will not affect the copied list, and vice-versa.

Java does not provide a built-in, general-purpose deep copy method. You have to implement it yourself.

Method 1: Manual Deep Copy (Recommended for Control)

You iterate through the original list and create new instances of each element.

Java List深拷贝与浅拷贝如何实现?-图3
(图片来源网络,侵删)

Scenario: You have a custom Person class that is mutable.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
// A mutable class
class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = 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
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}
public class DeepCloneExample {
    public static void main(String[] args) {
        List<Person> originalList = new ArrayList<>(Arrays.asList(
            new Person("Alice", 30),
            new Person("Bob", 25)
        ));
        // Create a deep copy manually
        List<Person> deepCopy = new ArrayList<>();
        for (Person originalPerson : originalList) {
            // Create a new Person object with the same data
            deepCopy.add(new Person(originalPerson.getName(), originalPerson.getAge()));
        }
        System.out.println("Original List: " + originalList);
        System.out.println("Deep Copy:     " + deepCopy);
        // Modify an object in the original list
        originalList.get(0).setName("Alicia");
        System.out.println("\nAfter modifying the first person in the original list:");
        System.out.println("Original List: " + originalList); // Name is now "Alicia"
        System.out.println("Deep Copy:     " + deepCopy);     // Name is still "Alice"
    }
}

Method 2: Serialization and Deserialization (General Purpose, but Risky)

This is a "clever" trick that can deep copy any Serializable object. It involves writing the object to a byte stream and then reading it back.

Warning:

  • It is very slow.
  • It requires all objects in the list and their nested objects to implement the Serializable interface.
  • It can fail if the object graph contains non-serializable parts.
  • It can lead to ClassCastExceptions if the class definitions change between serialization and deserialization.
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class DeepCopyViaSerialization {
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> List<T> deepCopy(List<T> original) {
        try {
            // 1. Write the original list to a byte array
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(original);
            oos.flush();
            // 2. Read the byte array back into a new list
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (List<T>) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("Failed to deep copy list via serialization", e);
        }
    }
    public static void main(String[] args) {
        List<String> originalList = new ArrayList<>(Arrays.asList("Hello", "World"));
        List<String> deepCopy = deepCopy(originalList);
        System.out.println("Original: " + originalList);
        System.out.println("Deep Copy: " + deepCopy);
        originalList.set(0, "Goodbye");
        System.out.println("\nAfter modifying original:");
        System.out.println("Original: " + originalList);
        System.out.println("Deep Copy: " + deepCopy); // Unchanged
    }
}

Summary Table

Method Type When to Use Performance Notes
new ArrayList<>(original) Shallow Default choice. Best for lists of immutable objects (String, Integer). Excellent Simple, fast, and standard.
list.addAll(original) Shallow Same as constructor. Good for adding to an existing list. Excellent Slightly more verbose.
stream().collect() Shallow When using a functional style. Good for read-only lists. Good Modern and concise.
Manual Loop Deep Recommended for deep copies. When you have mutable objects and need full control. Good (O(n)) Safe, explicit, and performant.
Serialization Deep A last resort for complex, nested Serializable objects where manual copy is hard. Very Poor Risky, slow, and brittle. Use with extreme caution.

Final Recommendation

  1. For most cases, use the constructor: new ArrayList<>(originalList). It's the standard, safest, and most efficient way to get a new, independent list.
  2. If your list contains mutable objects and you need a true deep copy, write a manual loop to create new instances of each element.
  3. Avoid the serialization trick unless you have a very specific reason and understand all the risks and performance implications.
分享:
扫描分享到社交APP
上一篇
下一篇