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 的深复制。


版权声明:本文为u012800199原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。