基于Spring的浅拷贝和深拷贝
- 浅拷贝
- 基于spring的浅拷贝
- 深拷贝
- 基于spring实现的深拷贝
浅拷贝
对当前对象进行克隆,并克隆该对象所包含的8种基本数据类型和String类型属性(拷贝一份该对象并重新分配内存,即产生了新的对象);但如果被克隆的对象中包含除8中数据类型和String类型外的其他类型的属性,浅克隆并不会克隆这些属性(即不会为这些属性分配内存,而是引用原来对象中的属性),
基于spring的浅拷贝
spring架构中提供了BeanUtils工具类
BeanUtils.copyProperties();
调用spring工具类中自带的方法,就可以实现浅拷贝
测试:
@Data
static
class A{
private String name;
private Integer age;
private Double money;
private B1 bname;
public A(String name,Integer age,Double money,String b1mp){
this.name = name;
this.age = age;
this.money = money;
this.bname = new B1(b1mp);
}
public A() {
}
}
@Data
static
class B1{
private String mp;
public B1(String mp) {
this.mp = mp;
}
public B1() {
}
}
public static void main(String[] args) {
A a = new A("张三",12,12.5,"张三B1");
A a1 = new A();
BeanUtils.copyProperties(a,a1);
a1.setName("李四");
a1.setAge(19);
a1.setMoney(19.8);
a1.getBname().setMp("李四B1");
System.out.println(a);
System.out.println(a1);
}
结果:
DeepBeanUtils.A(name=张三, age=12, money=12.5, bname=DeepBeanUtils.B1(mp=李四B1))
DeepBeanUtils.A(name=李四, age=19, money=19.8, bname=DeepBeanUtils.B1(mp=李四B1))
深拷贝
深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。对于对象中的对象属性,会重新创建对象,分配对象应用。
基于spring实现的深拷贝
/**
* 深拷贝对象
*
* @param source 源对象
* @param clazz 目标对象的类型
* @param excludeProperties 不需要拷贝的属性名
* @return 拷贝后的对象
*/
public static <T> T deepCopy(Object source, Class<T> clazz, String... excludeProperties) {
T target = null;
try {
//创建了一个目标对象 target
target = clazz.getDeclaredConstructor().newInstance();
//初始化了一个 HashMap 对象 map,用于存储已经深拷贝的源对象和目标对象的映射关系
Map<Object, Object> map = new HashMap<>();
deepCopyIterative(source, target, map, excludeProperties);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
return target;
}
/**
* 深拷贝对象
*
* @param source 源对象
* @param clazz 目标对象的类型
* @param map 存储已经拷贝过的对象的Map
* @param excludeProperties 不需要拷贝的属性名
* @return 拷贝后的对象
*/
private static void deepCopyIterative(Object source, Object target, Map<Object, Object> map, String... excludeProperties) {
if (source == null || target == null) {
return;
}
//两个 LinkedList 对象,分别存储源对象和目标对象
LinkedList<Object> sourceStack = new LinkedList<>();
LinkedList<Object> targetStack = new LinkedList<>();
//添加需要拷贝的对象
sourceStack.push(source);
targetStack.push(target);
while (!sourceStack.isEmpty()) {
//取出要拷贝的对象
Object currentSource = sourceStack.pop();
Object currentTarget = targetStack.pop();
//如果该属性被标记为排除属性,则不进行拷贝操作
if (currentSource == null || map.containsKey(currentSource)) {
continue;
}
//标记已经拷贝过的属性,避免循环引用
map.put(currentSource, currentTarget);
PropertyDescriptor[] sourcePds = BeanUtils.getPropertyDescriptors(currentSource.getClass());
for (PropertyDescriptor sourcePd : sourcePds) {
if (sourcePd.getReadMethod() != null && !"class".equals(sourcePd.getName())) {
PropertyDescriptor targetPd = BeanUtils.getPropertyDescriptor(currentTarget.getClass(), sourcePd.getName());
if (targetPd != null && targetPd.getWriteMethod() != null) {
String propertyName = sourcePd.getName();
if (Arrays.asList(excludeProperties).contains(propertyName)) {
continue;
}
try {
Object sourceValue = sourcePd.getReadMethod().invoke(currentSource);
if (sourceValue != null) {
Object targetValue = targetPd.getReadMethod().invoke(currentTarget);
//如果目标对象的属性值为 null,则根据属性类型进行赋值操作
if (targetValue == null) {
if (targetPd.getPropertyType().isPrimitive() || Number.class.isAssignableFrom(targetPd.getPropertyType())) {
targetValue = sourceValue;
} else {
targetValue = targetPd.getPropertyType().getDeclaredConstructor().newInstance();
}
targetPd.getWriteMethod().invoke(currentTarget, targetValue);
}
//将需要拷贝的对象添加到集合中
sourceStack.push(sourceValue);
targetStack.push(targetValue);
}
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | InstantiationException e) {
e.printStackTrace();
}
}
}
}
}
}
代码解释
这段代码的主要目的是实现对象的深度拷贝,其中使用了迭代的方法。
- 首先判断输入的源对象和目标对象是否为 null,若为 null 则直接返回。
- 定义两个 LinkedList 对象,sourceStack 和 targetStack,分别用于存储源对象和目标对象。
- 将源对象和目标对象分别压入 sourceStack 和 targetStack 栈中。
- 当 sourceStack 不为空时,执行以下操作:
a. 从 sourceStack 和 targetStack 中弹出当前的源对象和目标对象。
b. 如果当前源对象为空或已包含在 map 中,则跳过当前循环。
c. 将当前源对象放入 map,以避免循环引用。
d. 获取源对象的属性描述符数组。
e. 对于源对象的每一个属性描述符:
- 检查是否有读取方法并且属性名不是 “class”。
- 获取目标对象中与源对象属性名相同的属性描述符。
- 检查目标对象属性描述符是否有写入方法。
- 判断源属性名是否在排除属性列表中,如果在则跳过当前循环。
- 尝试获取源对象的属性值;如果属性值不为 null,则进行以下操作:
- 获取目标对象的属性值。
- 如果目标对象的属性值为 null,则根据属性类型进行赋值操作。具体而言,如果目标属性类型是基本类型或 Number 类型的子类,则直接将源对象的属性值赋给目标对象;否则,使用目标属性类型的构造方法创建一个新实例赋给目标对象。
- 将源对象的属性值压入 sourceStack,将目标对象的属性值压入 targetStack。
- 如果在执行上述操作过程中发生异常,打印异常堆栈信息。
这段代码的核心思想是使用栈实现对象深度拷贝的迭代过程,遍历源对象的每个属性,并将属性值复制到目标对象。在复制过程中,可以选择排除某些属性,以便某些属性不会被复制到目标对象。
测试
@Data
static
class A{
private String name;
private Integer age;
private Double money;
private B1 bname;
public A(String name,Integer age,Double money,String b1mp){
this.name = name;
this.age = age;
this.money = money;
this.bname = new B1(b1mp);
}
public A() {
}
}
@Data
static
class B1{
private String mp;
public B1(String mp) {
this.mp = mp;
}
public B1() {
}
}
public static void main(String[] args) {
A a = new A("张三",12,12.5,"张三B1");
A a1 = deepCopy(a, A.class);
a1.setName("李四");
a1.setAge(19);
a1.setMoney(19.8);
a1.getBname().setMp("李四B1");
System.out.println(a);
System.out.println(a1);
}
结果
DeepBeanUtils.A(name=张三, age=12, money=12.5, bname=DeepBeanUtils.B1(mp=张三B1))
DeepBeanUtils.A(name=李四, age=19, money=19.8, bname=DeepBeanUtils.B1(mp=李四B1))