1.浅拷贝
1.1 浅拷贝解释
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象
1.2 浅拷贝注意点
- 需要进行浅copy的类可实现Cloneable接口,实现clone方法
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
- 方法需要声明为public,如果声明为protected则只能在本包内使用(即本路径下的类)
- 如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了 super.Clone()方法),那么Object的clone()方法就会抛出CloneNotSupportedException异常。
2.深拷贝
浅拷贝只在根属性上在堆内存中创建了一个新的的对象,复制了基本类型的值,但是复杂数据类型也就是对象则是拷贝相同的地址,而深拷贝则是对于复杂数据类型在堆内存中开辟了一块内存地址用于存放复制的对象并且把原有的对象复制过来,这2个对象是相互独立的,也就是2个不同的地址
- 深copy需要自己new出对象实现
public ShallowClone deepClone() {
ShallowClone clone = new ShallowClone();
clone.name = this.name;
clone.age = this.age;
if (this.books != null) {
clone.books = new ArrayList<>(this.books);
}
return clone;
}
深拷贝实现方式
- 可以通过递归遍历,层层拷贝实现。但如果两个相互引用的对象深拷贝则会有不断递归导致爆栈。
- 序列化的方式来实现。如下代码所示,先把srcObj序列化成byte,再用stream去读取byte,再反序列化成cloneObj对象。需要注意的是序列化的类都要实现
public static Object depthClone(Object srcObj) {
Object cloneObj = null;
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(out);
oo.writeObject(srcObj);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream oi = new ObjectInputStream(in);
cloneObj = oi.readObject();
return cloneObj;
} catch (Exception ex) {
return null;
}
}
但序列化的方式有个大问题,序列化和反序列化都是比较耗时的操作,性能会比较差
- 反射方式,通过反射的方式实现对象拷贝的思路还是比较清晰的,先通过反射获取对象的所有属性,然后修改可访问级别,然后赋值;再获取继承的父类的属性,同样利用反射进行赋值
Apache的两个版本:
org.apache.commons.beanutils.PropertyUtils.copyProperties(Object dest, Object orig)
org.apache.commons.beanutils.BeanUtils#cloneBean
Spring版本:
org.springframework.beans.BeanUtils.copyProperties(Object source, Object target, Class editable, String[] ignoreProperties)
cglib版本:(使用动态代理,效率高)
net.sf.cglib.beans.BeanCopier.copy(Object paramObject1, Object paramObject2, Converter paramConverter)
一个反射例子
public static void copy(Object source, Object dest) throws Exception {
Class destClz = dest.getClass();
// 获取目标的所有成员
Field[] destFields = destClz.getDeclaredFields();
Object value;
for (Field field : destFields) { // 遍历所有的成员,并赋值
// 获取value值
value = getVal(field.getName(), source);
field.setAccessible(true);
field.set(dest, value);
}
}
private static Object getVal(String name, Object obj) throws Exception {
try {
// 优先获取obj中同名的成员变量
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(obj);
} catch (NoSuchFieldException e) {
// 表示没有同名的变量
}
// 获取对应的 getXxx() 或者 isXxx() 方法
name = name.substring(0, 1).toUpperCase() + name.substring(1);
String methodName = "get" + name;
String methodName2 = "is" + name;
Method[] methods = obj.getClass().getMethods();
for (Method method : methods) {
// 只获取无参的方法
if (method.getParameterCount() > 0) {
continue;
}
if (method.getName().equals(methodName)
|| method.getName().equals(methodName2)) {
return method.invoke(obj);
}
}
return null;
}
上面的实现步骤还是非常清晰的,首先是找同名的属性,然后利用反射获取对应的值
Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
return field.get(obj);
- 代理的方式实现对象拷贝
Cglib的BeanCopier就是通过代理的方式实现拷贝,性能优于反射的方式,特别是在大量的数据拷贝时,比较明显
代理,我们知道可以区分为静态代理和动态代理,简单来讲就是你要操作对象A,但是你不直接去操作A,而是找一个中转porxyA, 让它来帮你操作对象A
那么这种技术是如何使用在对象拷贝的呢?
我们知道,效率最高的对象拷贝方式就是Getter/Setter方法了,前面说的代理的含义指我们不直接操作,而是找个中间商来赚差价,那么方案就出来了
将原SourceA拷贝到目标DestB
创建一个代理 copyProxy
在代理中,依次调用 SourceA的get方法获取属性值,然后调用DestB的set方法进行赋值
实际上BeanCopier的思路大致如上,具体的方案当然就不太一样了, 简单看了一下实现逻辑,挺有意思的一块.
网友评论