对象拷贝,是一个很是基础的内容了,为何会单独的把这个领出来说解,主要是先前遇到了一个很是有意思的场景javascript
有一个任务,须要解析类xml标记语言,而后生成document对象,以后将会有一系列针对document对象的操做java
经过实际的测试,发现生成Document对象是比较耗时的一个操做,再加上这个任务场景中,须要解析的xml文档是固定的几个,那么一个能够优化的思路就是能不能缓存住建立后的Document对象,在实际使用的时候clone一份出来c++
看到了上面的应用背景,天然而言的就会想到深拷贝了,本篇博文则主要内容以下spring
深拷贝apache
至关于建立了一个新的对象,只是这个对象的全部内容,都和被拷贝的对象如出一辙而已,即二者的修改是隔离的,相互之间没有影响数组
浅拷贝缓存
也是建立了一个对象,可是这个对象的某些内容(好比A)依然是被拷贝对象的,即经过这两个对象中任意一个修改A,两个对象的A都会受到影响工具
看到上面两个简单的说明,那么问题来了性能
通常来讲,浅拷贝方式须要实现Cloneable
接口,下面结合一个实例,来看下浅拷贝中哪些是独立的,哪些是公用的测试
@Data public class ShallowClone implements Cloneable { private String name; private int age; private List<String> books; public ShallowClone clone() { ShallowClone clone = null; try { clone = (ShallowClone) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } public static void main(String[] args) { ShallowClone shallowClone = new ShallowClone(); shallowClone.setName("SourceName"); shallowClone.setAge(28); List<String> list = new ArrayList<>(); list.add("java"); list.add("c++"); shallowClone.setBooks(list); ShallowClone cloneObj = shallowClone.clone(); // 判断两个对象是否为同一个对象(便是否是新建立了一个实例) System.out.println(shallowClone == cloneObj); // 修改一个对象的内容是否会影响另外一个对象 shallowClone.setName("newName"); shallowClone.setAge(20); shallowClone.getBooks().add("javascript"); System.out.println("source: " + shallowClone.toString() + "\nclone:" + cloneObj.toString()); shallowClone.setBooks(Arrays.asList("hello")); System.out.println("source: " + shallowClone.toString() + "\nclone:" + cloneObj.toString()); } }
输出结果:
false source: ShallowClone(name=newName, age=20, books=[java, c++, javascript]) clone:ShallowClone(name=SourceName, age=28, books=[java, c++, javascript]) source: ShallowClone(name=newName, age=20, books=[hello]) clone:ShallowClone(name=SourceName, age=28, books=[java, c++, javascript])
结果分析:
其实,浅拷贝有个很是简单的理解方式:
浅拷贝的整个过程就是,建立一个新的对象,而后新对象的每一个值都是由原对象的值,经过 =
进行赋值
这个怎么理解呢?
上面的流程拆解就是:
- Object clone = new Object(); - clone.a = source.a - clone.b = source.b - ...
那么=赋值有什么特色呢?
基本数据类型是值赋值;非基本的就是引用赋值
深拷贝,就是要建立一个全新的对象,新的对象内部全部的成员也都是全新的,只是初始化的值已经由被拷贝的对象肯定了而已
那么上面的实例改为深拷贝应该是怎样的呢?
能够加上这么一个方法
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; } // 简单改一下测试case public static void main(String[] args) { ShallowClone shallowClone = new ShallowClone(); shallowClone.setName("SourceName"); shallowClone.setAge(new Integer(1280)); List<String> list = new ArrayList<>(); list.add("java"); list.add("c++"); shallowClone.setBooks(list); ShallowClone cloneObj = shallowClone.deepClone(); // 判断两个对象是否为同一个对象(便是否是新建立了一个实例) System.out.println(shallowClone == cloneObj); // 修改一个对象的内容是否会影响另外一个对象 shallowClone.setName("newName"); shallowClone.setAge(2000); shallowClone.getBooks().add("javascript"); System.out.println("source: " + shallowClone.toString() + "\nclone:" + cloneObj.toString()); shallowClone.setBooks(Arrays.asList("hello")); System.out.println("source: " + shallowClone.toString() + "\nclone:" + cloneObj.toString()); }
输出结果为:
false source: ShallowClone(name=newName, age=2000, books=[java, c++, javascript]) clone:ShallowClone(name=SourceName, age=1280, books=[java, c++]) source: ShallowClone(name=newName, age=2000, books=[hello]) clone:ShallowClone(name=SourceName, age=1280, books=[java, c++])
结果分析:
简单来讲,深拷贝是须要本身来实现的,对于基本类型能够直接赋值,而对于对象、容器、数组来说,须要建立一个新的出来,而后从新赋值
深拷贝的用途咱们很容易能够想见,某个复杂对象建立比较消耗资源的时候,就能够缓存一个蓝本,后续的操做都是针对深clone后的对象,这样就不会出现混乱的状况了
那么浅拷贝呢?感受留着是一个坑,一我的修改了这个对象的值,结果发现对另外一我的形成了影响,真不是坑爹么?
假设又这么一个通知对象长下面这样
private String notifyUser; // xxx private List<String> notifyRules;
咱们如今随机挑选了一千我的,同时发送通知消息,因此须要建立一千个上面的对象,这些对象中呢,除了notifyUser不一样,其余的都同样
在发送以前,忽然发现要临时新增一条通知信息,若是是浅拷贝的话,只用在任意一个通知对象的notifyRules中添加一调消息,那么这一千个对象的通知消息都会变成最新的了;而若是你是用深拷贝,那么苦逼的得遍历这一千个对象,每一个都加一条消息了
上面说到,浅拷贝,须要实现Clonebale接口,深拷贝通常须要本身来实现,那么我如今拿到一个对象A,它本身没有提供深拷贝接口,咱们除了主动一条一条的帮它实现以外,有什么辅助工具可用么?
对象拷贝区别与clone,它能够支持两个不一样对象之间实现内容拷贝
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);
若是找不到,则找getXXX, isXXX来获取
Cglib的BeanCopier就是经过代理的方式实现拷贝,性能优于反射的方式,特别是在大量的数据拷贝时,比较明显
代理,咱们知道能够区分为静态代理和动态代理,简单来说就是你要操做对象A,可是你不直接去操做A,而是找一个中转porxyA, 让它来帮你操做对象A
那么这种技术是如何使用在对象拷贝的呢?
咱们知道,效率最高的对象拷贝方式就是Getter/Setter方法了,前面说的代理的含义指咱们不直接操做,而是找个中间商来赚差价,那么方案就出来了
将原SourceA拷贝到目标DestB
实际上BeanCopier的思路大体如上,具体的方案固然就不太同样了, 简单看了一下实现逻辑,挺有意思的一块,先留个坑,后面单独开个博文补上
说明
从实现原理和经过简单的测试,发现BeanCopier是扫描原对象的getXXX方法,而后赋值给同名的 setXXX 方法,也就是说,若是这个对象中某个属性没有get/set方法,那么就没法赋值成功了
深拷贝
至关于建立了一个新的对象,只是这个对象的全部内容,都和被拷贝的对象如出一辙而已,即二者的修改是隔离的,相互之间没有影响
浅拷贝
也是建立了一个对象,可是这个对象的某些内容(好比A)依然是被拷贝对象的,即经过这两个对象中任意一个修改A,两个对象的A都会受到影响
经过反射方式实现对象拷贝
主要原理就是经过反射获取全部的属性,而后反射更改属性的内容
经过代理实现对象拷贝
将原SourceA拷贝到目标DestB
建立一个代理 copyProxy 在代理中,依次调用 SourceA的get方法获取属性值,而后调用DestB的set方法进行赋值
尽信书则不如,已上内容,纯属一家之言,因本人能力通常,看法不全,若有问题,欢迎批评指正