杰瑞科技汇

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

Of course! In Java, "cloning a list" can mean several different things, and it's crucial to understand the differences to choose the right method for your needs.

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

Let's break down the main ways to clone a List in Java, from the simplest to the most nuanced.

The Short Answer: Which One Should I Use?

Your Goal Best Method Why?
Create a new, independent copy new ArrayList<>(originalList); or originalList.stream().toList(); This is the most common and safest way. It creates a shallow copy of the list.
Create a deep copy Use a library like MapStruct or write a custom recursive method. A simple shallow copy is not enough if your list contains mutable objects (like custom classes).
Need the original list to be modified Collections.synchronizedList(new ArrayList<>(originalList)); You need a new list that is thread-safe, independent of the original.
Legacy code or specific needs originalList.clone(); (if ArrayList) or manual copying with a loop. Avoid clone() in new code. It's often misunderstood and not a clean API.

The Recommended Modern Way: Constructor Copy

This is the most idiomatic, readable, and recommended way to create a shallow copy of a list in modern Java (Java 9+ is preferred, but this works in Java 5+).

A shallow copy means the new list is a new object, but the elements inside it are the same references as the original list.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ListCloneExample {
    public static void main(String[] args) {
        // Original list of Strings
        List<String> originalList = new ArrayList<>(Arrays.asList("Apple", "Banana", "Cherry"));
        // --- Create a shallow copy using the constructor ---
        List<String> shallowCopy = new ArrayList<>(originalList);
        System.out.println("Original List: " + originalList);
        System.out.println("Shallow Copy:  " + shallowCopy);
        // --- Prove they are different objects ---
        System.out.println("\nAre they the same object? " + (originalList == shallowCopy)); // false
        // --- Prove the elements are the same (shallow copy) ---
        // Let's create a list of mutable objects to see the difference
        List<Person> people = new ArrayList<>();
        people.add(new Person("Alice"));
        people.add(new Person("Bob"));
        List<Person> peopleShallowCopy = new ArrayList<>(people);
        System.out.println("\nOriginal Person: " + people.get(0).name); // Alice
        System.out.println("Copied Person:   " + peopleShallowCopy.get(0).name); // Alice
        // Modify the object through the original list's reference
        people.get(0).setName("Alicia");
        System.out.println("\nAfter modifying original object:");
        System.out.println("Original Person: " + people.get(0).name); // Alicia
        System.out.println("Copied Person:   " + peopleShallowCopy.get(0).name); // Alicia (also changed!)
        // The list objects are different, but the Person object inside is shared.
        System.out.println("\nAre the Person objects the same? " + (people.get(0) == peopleShallowCopy.get(0))); // true
    }
}
class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }
}

When to use this:

  • Almost always. This is the default choice for creating a new, independent list.
  • When you want to prevent the caller from modifying your internal list by accident (defensive copying).
  • When you need to perform operations on a list without affecting the original.

The Java 8+ Stream Way

This is a very clean, functional approach that is also highly recommended. It's functionally identical to the constructor copy for a shallow copy.

Java List深拷贝与浅拷贝如何实现?-图2
(图片来源网络,侵删)
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamCloneExample {
    public static void main(String[] args) {
        List<Integer> originalList = Arrays.asList(1, 2, 3, 4, 5);
        // Create a shallow copy using a stream
        List<Integer> streamCopy = originalList.stream()
                                               .collect(Collectors.toList());
        System.out.println("Original List: " + originalList);
        System.out.println("Stream Copy:   " + streamCopy);
        // Prove they are different objects
        System.out.println("\nAre they the same object? " + (originalList == streamCopy)); // false
    }
}

Note: originalList.stream().toList() (Java 16+) is even shorter, but Collectors.toList() is more compatible with older Java 8 versions.

When to use this:

  • When you are already working with streams.
  • When you want to chain the copy operation with other stream transformations (e.g., filter, map).
  • It's just as good as the constructor method.

The clone() Method (Generally Avoid)

Java's Object.clone() method is often considered a "flawed design" and should be avoided in new code. While ArrayList and Vector implement it, the behavior can be surprising.

  • It's a shallow copy, just like the constructor method.
  • The return type is Object, so you must cast it.
  • It doesn't use the constructor, which can bypass initialization logic.
  • The Cloneable interface is just a marker interface with no actual methods, which is confusing.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CloneMethodExample {
    public static void main(String[] args) {
        List<String> originalList = new ArrayList<>(Arrays.asList("X", "Y", "Z"));
        // Use the clone() method
        @SuppressWarnings("unchecked") // We know it's an ArrayList, so the cast is safe
        List<String> clonedList = (List<String>) originalList.clone();
        System.out.println("Original List: " + originalList);
        System.out.println("Cloned List:   " + clonedList);
        // Prove they are different objects
        System.out.println("\nAre they the same object? " + (originalList == clonedList)); // false
    }
}

When to use this:

  • Almost never. The only reason might be if you are working with legacy code that relies on it or a specific framework that requires it.
  • Prefer the constructor or stream methods. They are clearer, safer, and more modern.

Creating a Deep Copy

A shallow copy is not enough when your list contains mutable objects (like your own custom classes, ArrayList, HashMap, etc.). If you modify the object in the original list, the change will be visible in the "copied" list because they both point to the same object.

A deep copy creates new copies of the objects inside the list as well.

Option A: Manual Deep Copy (The Hard Way)

You have to iterate and manually clone each element. This requires your objects to have a clone() method or a copy constructor.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Person implements Cloneable {
    String name;
    public Person(String name) {
        this.name = name;
    }
    // Add a copy constructor
    public Person(Person other) {
        this.name = other.name;
    }
    // Add a clone method
    @Override
    protected Person clone() {
        try {
            return (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(); // Can't happen
        }
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }
}
public class DeepCopyExample {
    public static void main(String[] args) {
        List<Person> originalPeople = new ArrayList<>();
        originalPeople.add(new Person("Charlie"));
        originalPeople.add(new Person("Diana"));
        // --- Create a deep copy manually ---
        List<Person> deepCopy = new ArrayList<>(originalPeople.size());
        for (Person person : originalPeople) {
            // Use the copy constructor or clone() method
            deepCopy.add(person.clone());
        }
        System.out.println("Original Person: " + originalPeople.get(0).name); // Charlie
        System.out.println("Deep Copied Person: " + deepCopy.get(0).name); // Charlie
        // Modify the object through the original list
        originalPeople.get(0).setName("Charles");
        System.out.println("\nAfter modifying original object:");
        System.out.println("Original Person: " + originalPeople.get(0).name); // Charles
        System.out.println("Deep Copied Person: " + deepCopy.get(0).name); // Charlie (unchanged!)
        // Now the objects are truly independent
        System.out.println("\nAre the Person objects the same? " + (originalPeople.get(0) == deepCopy.get(0))); // false
    }
}

Option B: Using a Library (The Recommended Way for Deep Copies)

Writing deep copy logic is tedious and error-prone. Libraries like MapStruct or ModelMapper are designed for this. They use code generation to create highly efficient and correct deep copy implementations.

Example with MapStruct:

  1. Add MapStruct to your project.
  2. Define an interface for the mapping.
// PersonMapper.java
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface PersonMapper {
    PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
    Person personToPerson(Person person); // MapStruct will auto-generate the implementation
}
// In your main code
List<Person> originalPeople = ...;
List<Person> deepCopy = originalPeople.stream()
                                      .map(PersonMapper.INSTANCE::personToPerson)
                                      .collect(Collectors.toList());

When to use a deep copy:

  • When you need to send a list of data to another part of the application (e.g., a different layer or thread) and must ensure that the original data cannot be corrupted through the copy.
  • When you want to perform complex operations on a list of objects without side effects on the original objects.

Summary Table

Method Type of Copy Readability When to Use
new ArrayList<>(originalList) Shallow Excellent Default choice. For creating an independent list that shares element references.
originalList.stream().toList() Shallow Excellent Default choice. When working with streams or for a functional style.
originalList.clone() Shallow Poor Avoid. Only for legacy code or specific, rare requirements.
Manual loop with element.clone() Deep Fair For simple objects when you can't/don't want to add a library. Can be complex and error-prone.
Library (e.g., MapStruct) Deep Excellent Recommended for deep copies. Robust, maintainable, and handles complex object graphs automatically.
分享:
扫描分享到社交APP
上一篇
下一篇