Java List中内容的深复制
Java 中有时需要复制 List 的内容到另一个 List,List 的复制有很多方法,但绝大多数是浅复制,下面对于不同方法进行验证,并找到深复制的方法。
测试类
首先定义一个类,用来测试:
class Student implements Cloneable {
private String name;
private int age;
public Student() {
}
public Student(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;
}
/**
* 重写clone方法,扩大访问范围,把protected改为public
*
* @return
* @throws CloneNotSupportedException
*/
@Override
public Object clone() throws CloneNotSupportedException {
Object object = super.clone();
return object;
}
/**
* 重写toString方法,输出内容
*
* @return
*/
@Override
public String toString() {
StringBuilder s = new StringBuilder("name:" + this.getName() + ",age:" + this.getAge());
return s.toString();
}
}
导入的类
import java.util.List;
import java.util.ArrayList;
import java.io.*;
1.遍历循环复制
用 ArrayList 的 add 方法将 list1 中的元素添加到 list2。
public static void main(String[] args) throws CloneNotSupportedException {
List<Student> list1 = new ArrayList<>();
List<Student> list2 = new ArrayList<>();
list1.add(new Student("张三", 31));
list1.add(new Student("李四", 32));
for (Student stu:list1) {
list2.add(stu);
}
list2.get(0).setName("西西");
list2.get(0).setAge(18);
System.out.println(list1);
System.out.println(list2);
}
输出结果:
[name:西西,age:18., name:李四,age:32.]
[name:西西,age:18., name:李四,age:32.]
结论:
对复制好的 list2 的对象进行更改,list1 的输出结果也改变,所以循环直接添加的方式是浅复制。
2.使用 clone() 方法
public static void main(String[] args) throws CloneNotSupportedException {
Student student1 = new Student("小二", 2);
Student student2 = (Student) student1.clone();
student2.setName("小五");
System.out.println(student1);
System.out.println(student2);
List<Student> list1 = new ArrayList<>();
List<Student> list2 = new ArrayList<>();
list1.add(new Student("张三", 31));
list1.add(new Student("李四", 32));
list2 = (ArrayList<Student>) ((ArrayList<Student>) list1).clone();
list2.get(0).setName("西西");
list2.get(0).setAge(18);
System.out.println(list1);
System.out.println(list2);
}
输出结果:
name:小二,age:2
name:小五,age:2
[name:西西,age:18, name:李四,age:32]
[name:西西,age:18, name:李四,age:32]
可以看到将 list2 的第一个对象修改之后,list1也发生了变化,是浅复制,进一步地,我们尝试循环遍历 list1,对其中的 Student 对象进行 clone()
public static void main(String[] args) throws CloneNotSupportedException {
List<Student> list1 = new ArrayList<>();
List<Student> list2 = new ArrayList<>();
list1.add(new Student("张三", 31));
list1.add(new Student("李四", 32));
for (int i = 0; i < list1.size(); i++) {
Student stuForCopy = (Student) list1.get(i).clone();
list2.add(stuForCopy);
}
list2.get(0).setName("西西");
list2.get(0).setAge(18);
System.out.println(list1);
System.out.println(list2);
}
输出结果:
[name:张三,age:31, name:李四,age:32]
[name:西西,age:18, name:李四,age:32]
结论:
clone() 方法是对被克隆对象的深复制,student2 是不同于 student1 的新对象,修改 student2 不影响 student1。list2 和 list1 是两个对象,但 clone 方法不会对 list 中的内容新建对象,而是直接引用。
所以通过遍历 list1 对其中的 Student 对象进行 clone() 可以达成深复制。
3.使用 list.addAll() 方法
public static void main(String[] args) throws CloneNotSupportedException {
List<Student> list1 = new ArrayList<>();
List<Student> list2 = new ArrayList<>();
list1.add(new Student("张三", 31));
list1.add(new Student("李四", 32));
list2.addAll(list1);
list2.get(0).setName("西西");
list2.get(0).setAge(18);
System.out.println(list1);
System.out.println(list2);
}
输出结果:
[name:西西,age:18, name:李四,age:32]
[name:西西,age:18, name:李四,age:32]
结论:
显然,list.addAll() 的方式与手动循环 add() 的方式结果一样,都是浅复制。
4.使用 Collections.copy() 方法
public static void main(String[] args) throws CloneNotSupportedException {
/**
* 注意:
* List初始化填数字,表示的是这个List的容量,并不是说其中就有了元素。
* list的capacity(容量)可以指定(最好指定)。而初始化时size的大小永远默认为0,只有在进行add和remove等相关操作 时,size的大小才变化。
* 然而进行copy()时候,首先会将list2的size和list1的size大小进行比较,只有当list2的size大于或者等于list1的size时才进行拷贝,否则抛出异常:
* java.lang.IndexOutOfBoundsException: Source does not fit in dest
* 因此如下实例通过Arrays.asList(new Student[list1.size()]);来进行初始化
*
*/
List<Student> list1 = new ArrayList<>();
list1.add(new Student("张三", 31));
list1.add(new Student("李四", 32));
List<Student> list2 = Arrays.asList(new Student[list1.size()]);
Collections.copy(list2, list1);
list2.get(0).setName("西西");
list2.get(0).setAge(18);
System.out.println(list1);
System.out.println(list2);
}
输出结果:
[name:西西,age:18, name:李四,age:32]
[name:西西,age:18, name:李四,age:32]
结论:
该方法也是浅复制。
5.使用序列化方法
把对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2) 在网络上传送对象的字节序列。
链接: 序列化和反序列化
public class Main {
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
List<Student> list1 = new ArrayList<>();
list1.add(new Student("张三", 31));
list1.add(new Student("李四", 32));
List<Student> list2 = deepCopy(list1);
list2.get(0).setName("西西");
list2.get(0).setAge(18);
System.out.println(list1);
System.out.println(list2);
}
/**
* 通过序列化的方式对list进行深复制
*
* @param src
* @param <T>
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
public static <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(src);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
@SuppressWarnings("unchecked")
List<T> dest = (List<T>) in.readObject();
return dest;
}
}
输出结果:
[name:张三,age:31, name:李四,age:32]
[name:西西,age:18, name:李四,age:32]
结论:
序列化的过程会跳出内存,所以很容理解通过序列化 list1,再反序列化形成 list2,list 中的内容对象肯定要重新建立,因此达成深复制的要求。
在工作中需要用到深复制的情况下,要确保 List 中的T类对象是不易被外部修改和破坏,通过
遍历克隆 T 对象
使用序列化方法
可以达成对 List 的深复制。