龙空技术网

Java学习之深拷贝浅拷贝及对象拷贝的两种思路

一灰灰blog 773

前言:

当前朋友们对“pornotifynet”大体比较着重,各位老铁们都需要学习一些“pornotifynet”的相关文章。那么小编也在网摘上收集了一些对于“pornotifynet””的相关知识,希望各位老铁们能喜欢,大家快快来了解一下吧!

I. Java之Clone

0. 背景

对象拷贝,是一个非常基础的内容了,为什么会单独的把这个领出来讲解,主要是先前遇到了一个非常有意思的场景

有一个任务,需要解析类xml标记语言,然后生成document对象,之后将会有一系列针对document对象的操作

通过实际的测试,发现生成Document对象是比较耗时的一个操作,再加上这个任务场景中,需要解析的xml文档是固定的几个,那么一个可以优化的思路就是能不能缓存住创建后的Document对象,在实际使用的时候clone一份出来

1. 内容说明

看到了上面的应用背景,自然而言的就会想到深拷贝了,本篇博文则主要内容如下

介绍下两种拷贝方式的区别

深拷贝的辅助工具类

如何自定义实现对象拷贝

II. 深拷贝和浅拷贝

0. 定义说明

深拷贝

相当于创建了一个新的对象,只是这个对象的所有内容,都和被拷贝的对象一模一样而已,即两者的修改是隔离的,相互之间没有影响

浅拷贝

也是创建了一个对象,但是这个对象的某些内容(比如A)依然是被拷贝对象的,即通过这两个对象中任意一个修改A,两个对象的A都会受到影响

看到上面两个简单的说明,那么问题来了

浅拷贝中,是所有的内容公用呢?还是某些内容公用?

从隔离来将,都不希望出现浅拷贝这种方式了,太容易出错了,那么两种拷贝方式的应用场景是怎样的?

1. 浅拷贝

一般来说,浅拷贝方式需要实现Cloneable接口,下面结合一个实例,来看下浅拷贝中哪些是独立的,哪些是公用的

输出结果:

结果分析:

拷贝后获取的是一个独立的对象,和原对象拥有不同的内存地址

基本元素类型,两者是隔离的(虽然上面只给出了int,String)

基本元素类型包括:

int, Integer, long, Long, char, Charset, byte,Byte, boolean, Boolean, float,Float, double, Double, String

非基本数据类型(如基本容器,其他对象等),只是拷贝了一份引用出去了,实际指向的依然是同一份

其实,浅拷贝有个非常简单的理解方式:

浅拷贝的整个过程就是,创建一个新的对象,然后新对象的每个值都是由原对象的值,通过=进行赋值

这个怎么理解呢?

上面的流程拆解就是:

那么=赋值有什么特点呢?

基本数据类型是值赋值;非基本的就是引用赋值

2. 深拷贝

深拷贝,就是要创建一个全新的对象,新的对象内部所有的成员也都是全新的,只是初始化的值已经由被拷贝的对象确定了而已

那么上面的实例改成深拷贝应该是怎样的呢?

可以加上这么一个方法

输出结果为:

结果分析:

深拷贝独立的对象

拷贝后对象的内容,与原对象的内容完全没关系,都是独立的

简单来说,深拷贝是需要自己来实现的,对于基本类型可以直接赋值,而对于对象、容器、数组来讲,需要创建一个新的出来,然后重新赋值

3. 应用场景区分

深拷贝的用途我们很容易可以想见,某个复杂对象创建比较消耗资源的时候,就可以缓存一个蓝本,后续的操作都是针对深clone后的对象,这样就不会出现混乱的情况了

那么浅拷贝呢?感觉留着是一个坑,一个人修改了这个对象的值,结果发现对另一个人造成了影响,真不是坑爹么?

假设又这么一个通知对象长下面这样

我们现在随机挑选了一千个人,同时发送通知消息,所以需要创建一千个上面的对象,这些对象中呢,除了notifyUser不同,其他的都一样

在发送之前,突然发现要临时新增一条通知信息,如果是浅拷贝的话,只用在任意一个通知对象的notifyRules中添加一调消息,那么这一千个对象的通知消息都会变成最新的了;而如果你是用深拷贝,那么苦逼的得遍历这一千个对象,每个都加一条消息了

III. 对象拷贝工具

上面说到,浅拷贝,需要实现Clonebale接口,深拷贝一般需要自己来实现,那么我现在拿到一个对象A,它自己没有提供深拷贝接口,我们除了主动一条一条的帮它实现之外,有什么辅助工具可用么?

对象拷贝区别与clone,它可以支持两个不同对象之间实现内容拷贝

Apache的两个版本:(反射机制)

Spring版本:(反射机制)

cglib版本:(使用动态代理,效率高)

从上面的几个有名的工具类来看,提供了两种使用者姿势,一个是反射,一个是动态代理,下面分别来看两种思路

1. 借助反射实现对象拷贝

通过反射的方式实现对象拷贝的思路还是比较清晰的,先通过反射获取对象的所有属性,然后修改可访问级别,然后赋值;再获取继承的父类的属性,同样利用反射进行赋值

上面的几个开源工具,内部实现封装得比较好,所以直接贴源码可能不太容易一眼就能看出反射方式的原理,所以简单的实现了一个, 仅提供思路

上面的实现步骤还是非常清晰的,首先是找同名的属性,然后利用反射获取对应的值

如果找不到,则找getXXX, isXXX来获取

2. 代理的方式实现对象拷贝

Cglib的BeanCopier就是通过代理的方式实现拷贝,性能优于反射的方式,特别是在大量的数据拷贝时,比较明显

代理,我们知道可以区分为静态代理和动态代理,简单来讲就是你要操作对象A,但是你不直接去操作A,而是找一个中转porxyA, 让它来帮你操作对象A

那么这种技术是如何使用在对象拷贝的呢?

我们知道,效率最高的对象拷贝方式就是Getter/Setter方法了,前面说的代理的含义指我们不直接操作,而是找个中间商来赚差价,那么方案就出来了

将原SourceA拷贝到目标DestB

创建一个代理 copyProxy

在代理中,依次调用 SourceA的get方法获取属性值,然后调用DestB的set方法进行赋值

实际上BeanCopier的思路大致如上,具体的方案当然就不太一样了, 简单看了一下实现逻辑,挺有意思的一块,先留个坑,后面单独开个博文补上

说明

从实现原理和通过简单的测试,发现BeanCopier是扫描原对象的getXXX方法,然后赋值给同名的 setXXX 方法,也就是说,如果这个对象中某个属性没有get/set方法,那么就无法赋值成功了

IV. 小结

1. 深拷贝和浅拷贝

深拷贝

相当于创建了一个新的对象,只是这个对象的所有内容,都和被拷贝的对象一模一样而已,即两者的修改是隔离的,相互之间没有影响

完全独立

浅拷贝

也是创建了一个对象,但是这个对象的某些内容(比如A)依然是被拷贝对象的,即通过这两个对象中任意一个修改A,两个对象的A都会受到影响

等同与新创建一个对象,然后使用=,将原对象的属性赋值给新对象的属性

需要实现Cloneable接口

2. 对象拷贝的两种方法

通过反射方式实现对象拷贝

主要原理就是通过反射获取所有的属性,然后反射更改属性的内容

通过代理实现对象拷贝

将原SourceA拷贝到目标DestB

创建一个代理 copyProxy 在代理中,依次调用 SourceA的get方法获取属性值,然后调用DestB的set方法进行赋值

V. 其他

声明

尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见解不全,如有问题,欢迎批评指正

标签: #pornotifynet