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.

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:
-
For a Shallow Copy (Elements are not copied):
new ArrayList<>(originalList)originalList.stream().collect(Collectors.toList())
-
For a Deep Copy (Elements are also copied):
(图片来源网络,侵删)- 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.

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 newArrayListand populates it by copying all the references fromoriginalList. - Pros: Simple, readable, and efficient.
- Cons: It's a shallow copy. If your list contains mutable objects (like a
Personclass), 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:
- 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. - Not Guaranteed by Interface: The
Listinterface does not mandate thatclone()must work. It's up to the concrete implementation (likeArrayList). WhileArrayList.clone()works, it's not a reliable, interface-driven approach. - Shallow Copy: It has the same shallow copy limitations as the constructor method.
- Returns
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 newList(usually anArrayList). - 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
Serializableobject. - 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
Serializableinterface.
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. |
