一、为什么需要深拷贝
对于Javalist,常常需要复制一个列表的内容到另外一个位置,这时候我们通常会使用List接口提供的copy方法进行复制。但是,使用copy方法复制出来的列表只是原列表的浅拷贝,即原列表和复制后的列表指向同一个内存地址,修改一个列表也会同时修改另一个列表。因此,在某些情况下,我们需要使用深拷贝来避免这种问题。
假设我们有一个List列表,其中存储的是自定义的Student对象,我们需要将该列表中的所有Student对象复制一份到另一个列表中。 如果直接使用copy方法,只是对Student对象进行了浅拷贝,如果我们在后面修改一个列表中的Student对象属性值,则另一个列表中的同一个对象的属性值也会同时修改,这样就会产生很多难以预测的问题。而使用深拷贝,则可以解决这个问题。
/** * 声明Student类,包含一个int类型的id属性以及一个String类型的name属性 */ private static class Student{ private int id; private String name; //构造函数 public Student(int id, String name) { this.id = id; this.name = name; } //getter和setter方法 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } public static void main(String[] args){ //创建一个List存储Student对象 ListstudentList = new ArrayList<>(); studentList.add(new Student(1, "张三")); studentList.add(new Student(2, "李四")); //使用copy方法进行浅拷贝 List copyList = new ArrayList (studentList); //修改原列表第一个元素的name值 studentList.get(0).setName("王五"); System.out.println(studentList.get(0).getName());//输出:王五 System.out.println(copyList.get(0).getName());//输出:王五 }
二、浅拷贝与深拷贝的区别
从例子中可以看出,使用copy方法只是对Student对象进行了浅拷贝。那么什么是浅拷贝,什么是深拷贝呢?
浅拷贝指的是拷贝一个对象,实际上只是拷贝了对象的引用(地址),新对象和原对象指向同一个内存地址。这样,在操作新对象时,原对象也会同时受到影响。而深拷贝则是将原对象的所有属性都复制到新对象中,并在堆内存中开辟新的空间存放新对象,新对象和原对象互不影响。
三、实现Javalist深拷贝
Javalist的深拷贝,可以通过以下几种方式:
1. Java8的stream流实现深拷贝
使用Java8新特性的stream流实现深拷贝,需要注意每个类需要实现接口Serializable,以便对象序列化。
//方法1:使用Java8的stream流实现深拷贝 ListcopyList1 = studentList.stream().map(student -> { Student s = new Student(student.getId(), student.getName()); return s; }).collect(Collectors.toList());
2. 使用BeanUtils实现深拷贝
使用Apache Commons BeanUtils提供的BeanUtils.copyProperties()方法实现深拷贝,需要注意每个类需实现接口Cloneable。
//方法2:使用BeanUtils实现深拷贝 ListcopyList2 = new ArrayList<>(); try { for (Student student : studentList) { Student newStudent = (Student) BeanUtils.cloneBean(student); copyList2.add(newStudent); } } catch (Exception e) { e.printStackTrace(); }
3. 使用序列化公共方法实现深拷贝
将对象序列化后再进行反序列化即可实现深拷贝,需要注意每个类必须实现Serializable接口。
//方法3:使用序列化公共方法实现深拷贝 ListcopyList3 = new ArrayList<>(); try { ByteArrayOutputStream bos = new ByteArrayOutputStream();//输出流 ObjectOutputStream oos = new ObjectOutputStream(bos);//对象输出流 oos.writeObject(studentList);//序列化 oos.flush(); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());//输入流 ObjectInputStream ois = new ObjectInputStream(bis);//对象输入流 copyList3 = (List ) ois.readObject();//反序列化 } catch (Exception e) { e.printStackTrace(); }
四、小结
在需要进行列表复制时,我们有时会遇到浅拷贝会带来的问题,因此需要使用深拷贝来解决这个问题。Java8的stream流、BeanUtils以及序列化都是实现Javalist深拷贝的有效方式。