如何在 Java 中深度複製 ArrayList
一、簡介
在這個簡短的教程中,我們將學習如何在 Java 中復制ArrayList
,重點介紹創建列表中元素的深層副本的不同方法。
2. 淺複製與深複製
淺複製技術複製原始對象,但僅複製可變字段的引用,而不復制實際對象。另一方面,深度複製創建所有可變字段的獨立副本,包括深度嵌套的對象。有關詳細指南,請參閱我們的文章深複製和淺複製之間的差異。
3. 型號
讓我們創建兩個類: Course
和Student
。 Student
類有一個Course
對象的實例作為可變依賴項:
public class Course {
private Integer courseId;
private String courseName;
// standard getters and setters
}
public class Student {
private int studentId;
private String studentName;
private Course course;
// standard getters and setters
}
4.使用Cloneable
接口進行深度複製
讓我們實現標記接口Cloneable
並重寫模型類中的clone
方法來創建深拷貝:
@Override
public Course clone() {
try {
return (Course) super.clone();
} catch (CloneNotSupportedException e) {
throw new IllegalStateException(e);
}
}
請注意, super.clone()
始終返回對象的淺表副本。在Course
類中,我們沒有任何可變字段,而在Student
類中,我們需要顯式設置可變字段來創建深層副本:
@Override
public Student clone() {
Student student;
try {
student = (Student) super.clone();
} catch (CloneNotSupportedException e) {
throw new IllegalStateException(e);
}
student.course = this.course.clone();
return student;
}
現在,讓我們迭代這些項目並使用clone
方法並驗證是否創建了深層副本:
public static List<Student> deepCopyUsingCloneable(List<Student> students){
return students.stream().map(Student::clone).collect(Collectors.toList());
}
@Test
public void whenCreatingCopyWithCloneable_thenObjectsShouldNotBeSame() {
Course course = new Course(1, "Spring Masterclass");
Student student1 = new Student(1, "John", course);
Student student2 = new Student(2, "David", course);
List<Student> students = new ArrayList<>();
students.add(student1);
students.add(student2);
List<Student> deepCopy = Student.deepCopyUsingCloneable(students);
Assertions.assertNotEquals(students.get(0), deepCopy.get(0));
Assertions.assertNotEquals(students.get(1), deepCopy.get(1));
}
5. 使用複制構造函數進行深複製
複製構造函數是一種特殊的構造函數,它接受其類類型的參數,並返回帶有傳遞值的新類實例。
讓我們為Student
對象創建一個複制構造函數,並使用它來對列表中的每個項目進行深層複製:
public Student(Student student) {
this.studentId = student.getStudentId();
this.studentName = student.getStudentName();
this.course = new Course(student.getCourse()
.getCourseId(), student.getCourse()
.getCourseName());
}
接下來,讓我們迭代列表中的項目,並使用上面創建的複制構造函數對列表中的每個項目進行深層複製並返回一個新列表:
public static List<Student> deepCopyUsingCopyConstructor(List<Student> students){
return students.stream().map(Student::new).collect(Collectors.toList());
}
在這種情況下,修改原始ArrayList
或列表中的元素不會對複制的列表產生任何影響,反之亦然:
@Test
public void whenCreatingDeepCopyWithCopyConstructor_thenObjectsShouldNotBeSame() {
Course course = new Course(1, "Spring Masterclass");
Student student1 = new Student(1, "John", course);
Student student2 = new Student(2, "David", course);
List<Student> students = new ArrayList<>();
students.add(student1);
students.add(student2);
List<Student> deepCopy = Student.deepCopyUsingCopyConstructor(students);
Assertions.assertNotEquals(students.get(0), deepCopy.get(0));
Assertions.assertNotEquals(students.get(1), deepCopy.get(1));
}
6. 使用 Apache Commons 庫進行深度複製
Apache Commons 庫提供了一個實用方法SerializationUtils.clone()
,可以幫助使用序列化和反序列化來深度複製對象。有關序列化的詳細指南,請參閱我們的文章 Java 序列化。
使用這種方法可確保所有字段(包括深層嵌套對象)都被複製,從而產生完全獨立的深層複製:
public static List<Student> deepCopyUsingSerialization(List<Student> students){
return students.stream().map(SerializationUtils::clone).collect(Collectors.toList());
}
對像圖中的所有對像都必須實現Serializable
接口才能成功。否則會拋出異常:
@Test
public void whenCreatingDeepCopyWithSerializationUtils_thenObjectsShouldNotBeSame() {
Course course = new Course(1, "Spring Masterclass");
Student student1 = new Student(1, "John", course);
Student student2 = new Student(2, "David", course);
List<Student> students = new ArrayList<>();
students.add(student1);
students.add(student2);
List<Student> deepCopy = Student.deepCopyUsingSerialization(students);
Assertions.assertNotEquals(students.get(0), deepCopy.get(0));
Assertions.assertNotEquals(students.get(1), deepCopy.get(1));
}
這使我們無需為複雜的對象結構創建克隆邏輯。但是,由於執行序列化和反序列化的開銷,它比其他方法稍慢。
最新版本的apache-commons-lang3庫可以在 Maven 中央存儲庫中找到。
7. 使用 Jackson 庫進行深度複製
Jackson 是另一個使用序列化和反序列化創建原始對象的深層副本的庫。它將對象序列化為 JSON 字符串,並將其反序列化回新的獨立副本:
public static Student createDeepCopy(Student student) {
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.readValue(objectMapper.writeValueAsString(student), Student.class);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
public static List<Student> deepCopyUsingJackson(List<Student> students) {
return students.stream().map(Student::createDeepCopy).collect(Collectors.toList());
}
請注意,Jackson 需要一個默認構造函數來序列化和反序列化任何給定的對象:
@Test
public void whenCreatingDeepCopyWithJackson_thenObjectsShouldNotBeSame() {
Course course = new Course(1, "Spring Masterclass");
Student student1 = new Student(1, "John", course);
Student student2 = new Student(2, "David", course);
List<Student> students = new ArrayList<>();
students.add(student1);
students.add(student2);
List<Student> deepCopy = Student.deepCopyUsingJackson(students);
Assertions.assertNotEquals(students.get(0), deepCopy.get(0));
Assertions.assertNotEquals(students.get(1), deepCopy.get(1));
}
最新版本的jackson-databind庫可以在 Maven 中央存儲庫中找到。
八、結論
在本教程中,我們介紹了複製ArrayList
的各種方法,包括本機方法和第三方庫的使用。與往常一樣,源代碼可以在 GitHub 上獲取。