Spring下的BeanUtils.copyProperties方法是深拷贝还是浅拷贝?
一、浅拷贝深拷贝的理解
简单地说,拷贝就是将一个类中的属性拷贝到另一个中,对于BeanUtils.copyProperties来说,必须保证属性名和类型是相同的,因为它是根据get和set方法来赋值的
1.1、浅拷贝
浅拷贝对于基本数据类型
就是直接进行值传递
,在内存的另一个空间内存放,修改这个值不会影响到拷贝源的值
浅拷贝对于引用数据类型
就是进行的是地址传递
,并没有对该对象重新开辟一个内存空间进行存放,所以对于引用数据类型的浅拷贝就相当于两个引用指向了同一个内存地址
浅拷贝可以理解为如果是引用类型,那么目标对象拷贝的只是源对象的地址,无论目标对象还是源对象改变,他们都会一起改变
1.2、深拷贝
深拷贝就是将目标对象的属性全部复制一份给源对象,复制完之后他们就是隔开的,没有任何关系,无论操作源对象还是目标对象都对另一个没有影响
无论是浅拷贝还是深拷贝,对于基本类型和String来说都是没有影响的,有影响的只有引用类型数据
二、测试浅拷贝与深拷贝
- 浅拷贝
package com.vinjcent;
public class Main {
public static void main(String[] args) {
// 主人(正)
Master main = new Master("男");
// 宠物
Pet p = new Pet("小狗", 5);
main.setPet(p);
// 浅拷贝一个"主"对象(副)
Master vice = main.clone();
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("拷贝后对象的指针是否都指向同一个堆内存: " + (main == vice));
System.out.println();
// 修改副对象中的性别属性
vice.setGender("女");
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("修改拷贝对象中的String类型的属性gender后是否相同: " + main.getGender().equals(vice.getGender()));
System.out.println();
// 修改副对象中引用对象中的年龄属性
vice.getPet().setAge(10);
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("修改拷贝对象中的引用类型Pet后,是否在同一个堆内存当中: " + (main.getPet() == vice.getPet()));
System.out.println();
System.out.println("可以发现,它是对'主'对象的拷贝,它不会拷贝'主'对象深层次的可变对象,只做第一层的拷贝");
}
}
// 宠物对象
class Pet {
private String name;
private int age;
public Pet(String name, int age) {
this.name = name;
this.age = age;
}
// ...省略set、get方法
@Override
public String toString() {
return "Pet{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
// 主人对象
class Master implements Cloneable {
private String gender;
private Pet pet;
public Master(String gender) {
this.gender = gender;
}
// ...省略set、get方法
// 浅拷贝
@Override
public Master clone() {
try {
// TODO: copy mutable state here, so the clone can't change the internals of the original
return (Master) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
@Override
public String toString() {
return "Master{" +
"gender='" + gender + '\'' +
", pet=" + pet +
'}';
}
}
- 输出结果
- 深拷贝
package com.vinjcent;
public class Main {
public static void main(String[] args) {
// 主人(正)
Master main = new Master("男");
// 宠物
Pet p = new Pet("小狗", 5);
main.setPet(p);
// 深拷贝一个"主"对象(副)
Master vice = main.clone();
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("拷贝后对象的指针是否都指向同一个堆内存: " + (main == vice));
System.out.println();
// 修改副对象中的性别属性
vice.setGender("女");
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("修改拷贝对象中的String类型的属性gender后是否相同: " + main.getGender().equals(vice.getGender()));
System.out.println();
// 修改副对象中引用对象中的年龄属性
vice.getPet().setAge(10);
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("修改拷贝对象中的引用类型Pet后,是否在同一个堆内存当中: " + (main.getPet() == vice.getPet()));
System.out.println();
System.out.println("可以发现,它是对'主'对象的完全拷贝,无论是引用对象还是数组,都会拷贝一个新的对象,并且与源对象的引用完全隔离");
}
}
// 宠物对象
class Pet implements Cloneable {
private String name;
private int age;
public Pet(String name, int age) {
this.name = name;
this.age = age;
}
// ...省略set、get方法
@Override
public String toString() {
return "Pet{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 浅拷贝
@Override
public Pet clone() {
try {
// TODO: copy mutable state here, so the clone can't change the internals of the original
return (Pet) super.clone(); // 不同点
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
// 主人对象
class Master implements Cloneable {
private String gender;
private Pet pet;
public Master(String gender) {
this.gender = gender;
}
// ...省略set、get方法
// 深拷贝
@Override
public Master clone() {
try {
// TODO: copy mutable state here, so the clone can't change the internals of the original
Master master = (Master) super.clone();
master.pet = pet.clone(); // 不同点
return master;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
@Override
public String toString() {
return "Master{" +
"gender='" + gender + '\'' +
", pet=" + pet +
'}';
}
}
- 输出结果
三、使用序列化实现深拷贝
什么是序列化? 把对象转换为字节序列的过程称为对象的序列化
什么是反序列化?把字节序列恢复为对象的过程称为对象的反序列化
为什么需要序列化?方便传输、存储,计算机数据传输是以字节为单位的,能处理所有类型的数据
通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深拷贝
【注】实现序列化的对象其类必须实现Serializable接口
- 代码演示
package com.vinjcent;
import java.io.*;
public class Main {
public static void main(String[] args) {
// 主人(正)
Master main = new Master("男");
// 宠物
Pet p = new Pet("小狗", 5);
main.setPet(p);
// 深拷贝一个"主"对象(副)
Master vice = main.clone();
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("拷贝后对象的指针是否都指向同一个堆内存: " + (main == vice));
System.out.println();
// 修改副对象中的性别属性
vice.setGender("女");
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("修改拷贝对象中的String类型的属性gender后是否相同: " + main.getGender().equals(vice.getGender()));
System.out.println();
// 修改副对象中引用对象中的年龄属性
vice.getPet().setAge(10);
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("修改拷贝对象中的引用类型Pet后,是否在同一个堆内存当中: " + (main.getPet() == vice.getPet()));
System.out.println();
System.out.println("可以发现,它是对'主'对象的序列化实现完全拷贝,无论是引用对象还是数组,都会拷贝一个新的对象,并且与源对象的引用完全隔离");
}
}
// 宠物对象
class Pet implements Serializable {
private String name;
private int age;
public Pet(String name, int age) {
this.name = name;
this.age = age;
}
// ...省略set、get方法
@Override
public String toString() {
return "Pet{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
// 主人对象
class Master implements Serializable {
private String gender;
private Pet pet;
public Master(String gender) {
this.gender = gender;
}
// ...省略set、get方法
public Master clone() {
ObjectOutputStream oos;
ObjectInputStream ois;
Master master = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ois = new ObjectInputStream(bais);
master = (Master) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return master;
}
@Override
public String toString() {
return "Master{" +
"gender='" + gender + '\'' +
", pet=" + pet +
'}';
}
}
- 输出结果
四、Spring下的BeanUtils.copyProperties()方法进行浅拷贝和深拷贝
对于BeanUtils.copyProperties来说,你必须保证属性名和类型是相同的,因为它是根据get和set方法来赋值的
但,BeanUtils只是浅拷贝
4.1 测试BeanUtils.copyProperties()浅拷贝
- 代码演示
package com.vinjcent;
import org.springframework.beans.BeanUtils;
public class Main {
public static void main(String[] args) {
// 主人(正)
Master main = new Master("男");
// 宠物
Pet p = new Pet("小狗", 5);
main.setPet(p);
// 浅拷贝一个"主"对象(副)
Master vice = new Master();
BeanUtils.copyProperties(main, vice); // 修改地方
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("拷贝后对象的指针是否都指向同一个堆内存: " + (main == vice));
System.out.println();
// 修改副对象中的性别属性
vice.setGender("女");
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("修改拷贝对象中的String类型的属性gender后是否相同: " + main.getGender().equals(vice.getGender()));
System.out.println();
// 修改副对象中引用对象中的年龄属性
vice.getPet().setAge(10);
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("修改拷贝对象中的引用类型Pet后,是否在同一个堆内存当中: " + (main.getPet() == vice.getPet()));
System.out.println();
}
}
// 宠物对象
class Pet {
private String name;
private int age;
public Pet(String name, int age) {
this.name = name;
this.age = age;
}
// ...省略set、get方法
@Override
public String toString() {
return "Pet{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
// 主人对象
class Master {
private String gender;
private Pet pet;
public Master(String gender) {
this.gender = gender;
}
public Master() {}
// ...省略set、get方法
@Override
public String toString() {
return "Master{" +
"gender='" + gender + '\'' +
", pet=" + pet +
'}';
}
}
- 输出结果
4.2 测试BeanUtils.copyProperties()深拷贝
从上面提到,我们可以通过序列化的形式来实现bean的深拷贝,可在BeanUtils.copyProperties()方法中如何实现呢?
答:我们可以借助java8的一些stream新特性
- 代码演示
package com.vinjcent;
import org.springframework.beans.BeanUtils;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
// 要是用stream流,一般会使用到集合,先构造一个master集合
List<Master> main = Arrays.asList(new Master("男"), new Master("女"));
main = main.stream().map(m -> {
m.setPet(new Pet("源pet", 5));
return m;
}).collect(Collectors.toList());
// 使用stream流实现bean的深拷贝
List<Master> vice = main
.stream()
.map(m -> {
Master temp = new Master();
BeanUtils.copyProperties(m, temp);
return temp;
}).collect(Collectors.toList());
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("拷贝后对象的指针是否都指向同一个堆内存: " + (main == vice));
System.out.println();
// 修改副对象中第一个元素的性别属性
vice.get(0).setGender("女");
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("在拷贝对象中,修改第一个元素的String类型的属性gender后是否相同: " + main.get(0).getGender().equals(vice.get(0).getGender()));
System.out.println();
// 修改副对象中第一个元素的引用对象中的年龄、名称属性
vice.get(0).getPet().setAge(10);
vice.get(0).getPet().setName("小猫");
System.out.println("主对象: " + main);
System.out.println("副对象: " + vice);
System.out.println("在拷贝对象中,修改第一个元素的引用类型Pet后,是否在同一个堆内存当中: " + (main.get(0).getPet() == vice.get(0).getPet()));
System.out.println();
System.out.println("可以发现,它是对'主'对象的拷贝,它不会拷贝'主'对象深层次的可变对象,只做第一层的拷贝");
}
}
// 宠物对象
class Pet {
private String name;
private int age;
public Pet(String name, int age) {
this.name = name;
this.age = age;
}
// ...省略set、get方法
@Override
public String toString() {
return "Pet{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
// 主人对象
class Master {
private String gender;
private Pet pet;
public Master(String gender) {
this.gender = gender;
}
public Master() {
}
// ...省略set、get方法
@Override
public String toString() {
return "Master{" +
"gender='" + gender + '\'' +
", pet=" + pet +
'}';
}
}
- 输出结果
结论:由此可知,org.springframework.beans.BeanUtils工具类中的copyProperties()方法无法实现深拷贝,只能实现浅拷贝