杰瑞科技汇

Java如何实现list clone?深拷贝与浅拷贝区别?

Of course! In Java, "cloning" a list means creating a new list that contains the same elements as the original list. There are several ways to do this, each with different behaviors regarding the elements themselves.

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

Here’s a comprehensive guide covering the most common and effective methods.


Quick Answer: The Best Ways to Clone a List

For most use cases, you'll want one of these two methods:

  1. For a Shallow Copy (Elements are not copied):

    • new ArrayList<>(originalList)
    • originalList.stream().collect(Collectors.toList())
  2. For a Deep Copy (Elements are also copied):

    Java如何实现list clone?深拷贝与浅拷贝区别?-图2
    (图片来源网络,侵删)
    • Serialization / Deserialization (The most robust, standard way for any object).

Understanding Shallow vs. Deep Copy

This is the most important concept to understand before choosing a cloning method.

  • Shallow Copy: Creates a new list object, but the elements inside the new list are the same references to the objects in the original list.

    • If you modify an element in the new list, it will also be modified in the original list.
    • Use Case: When the elements are immutable (like String, Integer, Double) or when you don't mind the lists sharing the same element objects.
  • Deep Copy: Creates a new list object and new copies of all the elements inside it.

    • The new list and its elements are completely independent of the original list.
    • Use Case: When you need a fully independent copy, especially if the elements are mutable (like custom objects, ArrayList, HashMap).

Method 1: Constructor (Most Common & Recommended for Shallow Copy)

This is the simplest and most idiomatic way to create a shallow copy of a list in Java.

Java如何实现list clone?深拷贝与浅拷贝区别?-图3
(图片来源网络,侵删)
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ListCloneConstructor {
    public static void main(String[] args) {
        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.set(0, "Apricot");
        System.out.println("\nAfter modifying original list:");
        System.out.println("Original List: " + originalList); // [Apricot, Banana, Cherry]
        System.out.println("Shallow Copy:  " + shallowCopy);  // [Apple, Banana, Cherry] -> Unaffected
        // Modify an element in the copy (if elements were mutable, this would affect the original)
        // Since String is immutable, this just points the copy's reference to a new String object.
        shallowCopy.set(1, "Blueberry");
        System.out.println("\nAfter modifying copy list:");
        System.out.println("Original List: " + originalList); // [Apricot, Banana, Cherry] -> Unaffected
        System.out.println("Shallow Copy:  " + shallowCopy);  // [Apple, Blueberry, Cherry]
    }
}
  • How it works: The new ArrayList<>(originalList) constructor creates a new ArrayList and populates it by copying all the references from originalList.
  • Pros: Simple, readable, and efficient.
  • Cons: It's a shallow copy. If your list contains mutable objects (like a Person class), modifying that object in the copy will modify it in the original.

Example with Mutable Objects (The Pitfall of Shallow Copy)

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Person {
    String name;
    public Person(String name) { this.name = name; }
    @Override public String toString() { return name; }
}
public class ShallowCopyPitfall {
    public static void main(String[] args) {
        List<Person> originalList = new ArrayList<>(Arrays.asList(new Person("Alice"), new Person("Bob")));
        // Shallow copy
        List<Person> shallowCopy = new ArrayList<>(originalList);
        System.out.println("Original: " + originalList);
        System.out.println("Copy:     " + shallowCopy);
        // Modify the Person object in the copy
        shallowCopy.get(0).name = "Anna";
        System.out.println("\nAfter modifying the Person in the copy:");
        System.out.println("Original: " + originalList); // [Anna, Bob] <-- AFFECTED!
        System.out.println("Copy:     " + shallowCopy);  // [Anna, Bob]
    }
}

As you can see, changing the Person object in the shallowCopy list also changed it in the originalList because both lists hold references to the exact same Person objects.


Method 2: clone() (Not Recommended)

The List interface inherits a clone() method from the Object class. However, its behavior is often confusing and not recommended for general use.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ListCloneMethod {
    public static void main(String[] args) {
        List<String> originalList = new ArrayList<>(Arrays.asList("A", "B", "C"));
        // Using the clone() method
        List<String> clonedList = (List<String>) originalList.clone();
        System.out.println("Original: " + originalList);
        System.out.println("Cloned:   " + clonedList);
    }
}
  • How it works: This method creates a shallow copy, just like the constructor.
  • Cons:
    1. Returns Object: You must cast the result to the specific list type ((List<String>)), which is clunky and can lead to runtime errors if you're not careful.
    2. Not Guaranteed by Interface: The List interface does not mandate that clone() must work. It's up to the concrete implementation (like ArrayList). While ArrayList.clone() works, it's not a reliable, interface-driven approach.
    3. Shallow Copy: It has the same shallow copy limitations as the constructor method.

Conclusion: Avoid clone(). Use the constructor method for a shallow copy.


Method 3: Java 8 Streams (Functional Approach)

This is a modern, functional way to create a shallow copy. It's very expressive and integrates well with other stream operations.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class ListCloneStream {
    public static void main(String[] args) {
        List<String> originalList = new ArrayList<>(Arrays.asList("X", "Y", "Z"));
        // Create a shallow copy using Streams
        List<String> streamCopy = originalList.stream()
                                               .collect(Collectors.toList());
        System.out.println("Original: " + originalList);
        System.out.println("Stream Copy: " + streamCopy);
    }
}
  • How it works: stream() converts the list to a stream of elements. collect(Collectors.toList()) is a terminal operation that gathers all elements from the stream into a new List (usually an ArrayList).
  • Pros: Very flexible, can be easily combined with filtering, mapping, etc. (e.g., originalList.stream().filter(s -> s.startsWith("A")).collect(Collectors.toList())).
  • Cons: It's a shallow copy. It can be slightly less performant for simple cloning due to the stream overhead, but this is usually negligible.

Method 4: Deep Copy (Serialization/Deserialization)

This is the standard and most reliable way to create a true deep copy of a list and its elements. It works by converting the object to a byte stream and then reading it back to create a new object.

Step 1: Make your objects Serializable

import java.io.Serializable;
class Person implements Serializable { // Must implement Serializable
    private static final long serialVersionUID = 1L; // Good practice
    String name;
    public Person(String name) { this.name = name; }
    @Override public String toString() { return name; }
}

Step 2: Create a utility method for deep cloning

import java.io.*;
import java.util.List;
public class DeepCopyUtil {
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> List<T> deepCopy(List<T> originalList) {
        try {
            // 1. Write the object to a byte array
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(originalList);
            oos.flush();
            // 2. Read the object from the byte array
            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", e);
        }
    }
}

Step 3: Use the utility method

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class DeepCopyExample {
    public static void main(String[] args) {
        List<Person> originalList = new ArrayList<>(Arrays.asList(new Person("David"), new Person("Eve")));
        // Create a deep copy
        List<Person> deepCopy = DeepCopyUtil.deepCopy(originalList);
        System.out.println("Original: " + originalList);
        System.out.println("Deep Copy: " + deepCopy);
        // Modify the Person object in the deep copy
        deepCopy.get(0).name = "Dan";
        System.out.println("\nAfter modifying the Person in the deep copy:");
        System.out.println("Original: " + originalList); // [David, Eve] -> UNAFFECTED!
        System.out.println("Deep Copy: " + deepCopy);  // [Dan, Eve]
    }
}
  • Pros: Creates a true, independent copy. Works for any Serializable object.
  • Cons:
    • Performance: Slower than shallow copy methods due to I/O operations.
    • Complexity: Requires boilerplate code and error handling.
    • Limitation: All objects in the list (and their nested objects) must implement the Serializable interface.

Summary Table

Method Type of Copy Performance Ease of Use Key Consideration
new ArrayList<>(list) Shallow Excellent Excellent Recommended for shallow copies. Elements are shared.
list.clone() Shallow Excellent Good Avoid. Clunky casting and not interface-guaranteed.
list.stream().collect(...) Shallow Good Excellent Great for functional programming and combined operations.
Serialization/Deserialization Deep Poor Fair Recommended for deep copies. Requires Serializable objects.
分享:
扫描分享到社交APP
上一篇
下一篇