参考文章:https://blog.csdn.net/qq_39327985/article/details/100116148
本人在项目中遇到过浅拷贝深拷贝的坑,使用一个对象以为是深拷贝,结果修改成员变量对象,导致两边内容均被修改。本文主要简介浅拷贝和深拷贝的概念以及三种实现深拷贝的方法。
Java 中的数据类型分为基本数据类型和引用数据类型。对于这两种数据类型,在进行赋值操作、用作方法参数或返回值时,会有值传递和引用(地址)传递的差别。
比如一个bean如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Company {
private String id;
private List<Employee> employees;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class Employee{
private String name;
}
}
写demo进行测试:
public static void main(String[] args) throws Exception {
List<Company.Employee> emps = Lists.newArrayList(Company.Employee.builder().name("one").build());
Company company1 = Company.builder().id("1").employees(emps).build();
// 直接复制,company1和2相当于同一实例
Company company2 = company1;
System.out.println("company1 hashcode: " + company1.hashCode());
System.out.println("company2 hashcode: " + company2.hashCode());
System.out.println("company1 is: " + company1);
System.out.println("company2 is: " + company2);
System.out.println("company1 id hashcode: " + company1.getId().hashCode());
System.out.println("company2 id hashcode: " + company2.getId().hashCode());
company2.setId("2");
company2.getEmployees().get(0).setName("two");
System.out.println("change id hashcode: " + company1.getId().hashCode());
System.out.println("change id hashcode: " + company2.getId().hashCode());
System.out.println("change company1 is: " + company1);
System.out.println("change company2 is: " + company2);
}
输出结果:
company1 hashcode: 116644
company2 hashcode: 116644
company1 is: Company(id=1, employees=[Company.Employee(name=one)])
company2 is: Company(id=1, employees=[Company.Employee(name=one)])
company1 id hashcode: 49
company2 id hashcode: 49
change id hashcode: 50
change id hashcode: 50
change company1 is: Company(id=2, employees=[Company.Employee(name=two)])
change company2 is: Company(id=2, employees=[Company.Employee(name=two)])
将直接赋值改为重新构造,本处使用了builder构造,实际上在Company类中写一个构造函数也是一样的效果,public Company(Company company) {this.id = company.getId();this.employees = company.getEmployees();}。
public static void main(String[] args) throws Exception {
List<Company.Employee> emps = Lists.newArrayList(Company.Employee.builder().name("one").build());
Company company1 = Company.builder().id("1").employees(emps).build();
Company company2 = Company.builder().id(company1.getId()).employees(company1.getEmployees()).build();
System.out.println("company1 hashcode: " + company1.hashCode());
System.out.println("company2 hashcode: " + company2.hashCode());
System.out.println("company1 is: " + company1);
System.out.println("company2 is: " + company2);
System.out.println("company1 id hashcode: " + company1.getId().hashCode());
System.out.println("company2 id hashcode: " + company2.getId().hashCode());
company2.setId("2");
company2.getEmployees().get(0).setName("two");
System.out.println("change id hashcode: " + company1.getId().hashCode());
System.out.println("change id hashcode: " + company2.getId().hashCode());
System.out.println("change company1 is: " + company1);
System.out.println("change company2 is: " + company2);
}
输出结果:
company1 hashcode: 116644
company2 hashcode: 116644
company1 is: Company(id=1, employees=[Company.Employee(name=one)])
company2 is: Company(id=1, employees=[Company.Employee(name=one)])
company1 id hashcode: 49
company2 id hashcode: 49
change id hashcode: 49
change id hashcode: 50
change company1 is: Company(id=1, employees=[Company.Employee(name=two)])
change company2 is: Company(id=2, employees=[Company.Employee(name=two)])
可以看出,id这种基本数据类型进行了值传递,但是employees数组是引用传递,改变company2数组中内容,company1和company2中都改变了。
// org.springframework.beans.BeanUtils
BeanUtils.copyProperties(company1, company2);
// org.apache.commons.beanutils.BeanUtils
BeanUtils.copyProperties(company2, company1);
注意:两个包的方法的参数是相反的。org.springframework.beans.BeanUtils中第一个参数是源,第二个参数是目标,org.apache.commons.beanutils.BeanUtils则相反
输出的结果与上述相同,也是浅拷贝。
从刚刚的实例看到,数组我们直接赋值,是浅拷贝,那么如下几种情况的数组处理,都是浅拷贝
List<Company.Employee> list1 = new ArrayList<>();
company1.getEmployees().forEach(c -> list1.add(c));
进行demo测试
List<Company.Employee> emps = Lists.newArrayList(Company.Employee.builder().name("one").build());
Company company1 = Company.builder().id("1").employees(emps).build();
System.out.println("orignal list is: " + company1.getEmployees());
List<Company.Employee> list1 = new ArrayList<>();
company1.getEmployees().forEach(c -> list1.add(c));
list1.get(0).setName("list1");
System.out.println("list is: " + company1.getEmployees());
System.out.println("list1 is: " + list1);
List<Company.Employee> list1 = new ArrayList<>();
list1.addAll(company1.getEmployees());
进行demo测试
List<Company.Employee> emps = Lists.newArrayList(Company.Employee.builder().name("one").build());
Company company1 = Company.builder().id("1").employees(emps).build();
System.out.println("orignal list is: " + company1.getEmployees());
List<Company.Employee> list1 = new ArrayList<>();
list1.addAll(company1.getEmployees());
list1.get(0).setName("list1");
System.out.println("list is: " + company1.getEmployees());
System.out.println("list1 is: " + list1);
List<Company.Employee> list1 = new ArrayList<>(company1.getEmployees());
进行demo测试
List<Company.Employee> emps = Lists.newArrayList(Company.Employee.builder().name("one").build());
Company company1 = Company.builder().id("1").employees(emps).build();
System.out.println("orignal list is: " + company1.getEmployees());
List<Company.Employee> list1 = new ArrayList<>(company1.getEmployees());
list1.get(0).setName("list1");
System.out.println("list is: " + company1.getEmployees());
System.out.println("list1 is: " + list1);

代码如下
System.arraycopy(temp, 0, list1, 0, temp.length);
进行demo测试
List<Company.Employee> emps = Lists.newArrayList(Company.Employee.builder().name("one").build());
Company company1 = Company.builder().id("1").employees(emps).build();
System.out.println("orignal list is: " + company1.getEmployees());
Object[] temp = company1.getEmployees().toArray();
Company.Employee[] list1 = new Company.Employee[temp.length];
System.arraycopy(temp, 0, list1, 0, temp.length);
list1[0].setName("list1");
System.out.println("list is: " + company1.getEmployees());
System.out.println("list1 is: " + list1[0].getName());
本组输出结果
orignal list is: [Company.Employee(name=one)]
list is: [Company.Employee(name=list1)]
list1 is: list1
上述1 2 3 几组测试的结果均输出如下,即后者改变了,前者也跟着改变。
orignal list is: [Company.Employee(name=one)]
list is: [Company.Employee(name=list1)]
list1 is: [Company.Employee(name=list1)]
重写Company里clone
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Company implements Cloneable{
private String id;
private List<Employee> employees;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class Employee implements Cloneable{
private String name;
@Override
public Employee clone() {
//浅拷贝
try {
// 直接调用父类的clone()方法
return (Employee) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
/**
* 重写clone()方法
* @return
*/
@Override
public Company clone() {
//浅拷贝
try {
Object cloneSuper = super.clone();
Company res = (Company) cloneSuper;
List<Employee> employeesCopy = Lists.newArrayList();
this.employees.forEach(e ->{
employeesCopy.add((Employee) e.clone());
});
res.setEmployees(employeesCopy);
return res;
} catch (CloneNotSupportedException e) {
return null;
}
}
}
进行测试
public static void main(String[] args) throws Exception {
List<Company.Employee> emps = Lists.newArrayList(Company.Employee.builder().name("one").build());
Company company1 = Company.builder().id("1").employees(emps).build();
// 调用重写的clone
Company company2 = company1.clone();
System.out.println("company1 hashcode: " + company1.hashCode());
System.out.println("company2 hashcode: " + company2.hashCode());
System.out.println("company1 is: " + company1);
System.out.println("company2 is: " + company2);
System.out.println("company1 id hashcode: " + company1.getId().hashCode());
System.out.println("company2 id hashcode: " + company2.getId().hashCode());
company2.setId("2");
company2.getEmployees().get(0).setName("two");
System.out.println("change id hashcode: " + company1.getId().hashCode());
System.out.println("change id hashcode: " + company2.getId().hashCode());
System.out.println("change company1 is: " + company1);
System.out.println("change company2 is: " + company2);
}
输出结果
company1 hashcode: 116644
company2 hashcode: 116644
company1 is: Company(id=1, employees=[Company.Employee(name=one)])
company2 is: Company(id=1, employees=[Company.Employee(name=one)])
company1 id hashcode: 49
company2 id hashcode: 49
change id hashcode: 49
change id hashcode: 50
change company1 is: Company(id=1, employees=[Company.Employee(name=one)])
change company2 is: Company(id=2, employees=[Company.Employee(name=two)])
可以看出改变company2里数组的值,不会影响company1,已经实现了深拷贝
注意
effective java中13条提醒我们:谨慎地重写clone方法。
考虑到与 Cloneable 接口相关的所有问题,新的接口不应该继承它,新的可扩展类不应该实现它。 虽然实现Cloneable接口对于final类没有什么危害,但应该将其视为性能优化的角度(会浪费复制),仅在极少数情况下才是合理的(详见第67条)。 通常,复制功能最好由构造方法或工厂提供。 这个规则的一个明显的例外是数组,它最好用clone方法复制。
因此不建议这样实现深拷贝。
需要将bean implements Serializable,如果有某个属性不需要序列化,可以将其声明为transient。
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Company implements Serializable {
private String id;
private List<Employee> employees;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class Employee implements Serializable {
private String name;
}
//深度拷贝
public Company deepCopy() throws Exception{
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
oos.flush();
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Company)ois.readObject();
}
}
进行测试
public static void main(String[] args) throws Exception {
List<Company.Employee> emps = Lists.newArrayList(Company.Employee.builder().name("one").build());
Company company1 = Company.builder().id("1").employees(emps).build();
// 调用深拷贝方法
Company company2 = company1.deepCopy();
System.out.println("company1 hashcode: " + company1.hashCode());
System.out.println("company2 hashcode: " + company2.hashCode());
System.out.println("company1 is: " + company1);
System.out.println("company2 is: " + company2);
System.out.println("company1 id hashcode: " + company1.getId().hashCode());
System.out.println("company2 id hashcode: " + company2.getId().hashCode());
company2.setId("2");
company2.getEmployees().get(0).setName("two");
System.out.println("change id hashcode: " + company1.getId().hashCode());
System.out.println("change id hashcode: " + company2.getId().hashCode());
System.out.println("change company1 is: " + company1);
System.out.println("change company2 is: " + company2);
}
输出结果
company1 hashcode: 116644
company2 hashcode: 116644
company1 is: Company(id=1, employees=[Company.Employee(name=one)])
company2 is: Company(id=1, employees=[Company.Employee(name=one)])
company1 id hashcode: 49
company2 id hashcode: 49
change id hashcode: 49
change id hashcode: 50
change company1 is: Company(id=1, employees=[Company.Employee(name=one)])
change company2 is: Company(id=2, employees=[Company.Employee(name=two)])
本人比较推荐这种方法进行深拷贝。
Orika MapperFacade中有两个核心的类,MapperFactory 、MapperFacade。
具体关于Orika MapperFacade的用法见Orika使用
bean还原成最初的样子
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Company {
private String id;
private List<Employee> employees;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public static class Employee {
private String name;
}
}
测试方法,写在controller层,因为mapperFacade需要注入,不能直接声明为static,因此没有使用main方法中测试
List<Company.Employee> emps = Lists.newArrayList(Company.Employee.builder().name("one").build());
Company company1 = Company.builder().id("1").employees(emps).build();
// 使用mapper进行转换
Company company2 = mapperFacade.map(company1, Company.class);
System.out.println("company1 hashcode: " + company1.hashCode());
System.out.println("company2 hashcode: " + company2.hashCode());
System.out.println("company1 is: " + company1);
System.out.println("company2 is: " + company2);
System.out.println("company1 id hashcode: " + company1.getId().hashCode());
System.out.println("company2 id hashcode: " + company2.getId().hashCode());
company2.setId("2");
company2.getEmployees().get(0).setName("two");
System.out.println("change id hashcode: " + company1.getId().hashCode());
System.out.println("change id hashcode: " + company2.getId().hashCode());
System.out.println("change company1 is: " + company1);
System.out.println("change company2 is: " + company2);
结果输出
company1 hashcode: 116644
company2 hashcode: 116644
company1 is: Company(id=1, employees=[Company.Employee(name=one)])
company2 is: Company(id=1, employees=[Company.Employee(name=one)])
company1 id hashcode: 49
company2 id hashcode: 49
change id hashcode: 49
change id hashcode: 50
change company1 is: Company(id=1, employees=[Company.Employee(name=one)])
change company2 is: Company(id=2, employees=[Company.Employee(name=two)])