java对象拷贝详解及实例
发布时间 - 2026-01-11 00:44:53 点击率:次java对象拷贝详解及实例

Java赋值是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的:
@Test
public void testassign(){
Person p1=new Person();
p1.setAge(31);
p1.setName("Peter");
Person p2=p1;
System.out.println(p1==p2);//true
}
如果创建一个对象的新的副本,也就是说他们的初始状态完全一样,但以后可以改变各自的状态,而互不影响,就需要用到java中对象的复制,如原生的clone()方法。
如何进行对象克隆
Object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected的,所以实体类使用克隆的前提是:
① 实现Cloneable接口,这是一个标记接口,自身没有方法。
② 覆盖clone()方法,可见性提升为public。
@Data
public class Person implements Cloneable {
private String name;
private Integer age;
private Address address;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Test
public void testShallowCopy() throws Exception{
Person p1=new Person();
p1.setAge(31);
p1.setName("Peter");
Person p2=(Person) p1.clone();
System.out.println(p1==p2);//false
p2.setName("Jacky");
System.out.println("p1="+p1);//p1=Person [name=Peter, age=31]
System.out.println("p2="+p2);//p2=Person [name=Jacky, age=31]
}
该测试用例只有两个基本类型的成员,测试达到目的了。
事情貌似没有这么简单,为Person增加一个Address类的成员:
@Data
public class Address {
private String type;
private String value;
}
再来测试,问题来了。
@Test
public void testShallowCopy() throws Exception{
Address address=new Address();
address.setType("Home");
address.setValue("北京");
Person p1=new Person();
p1.setAge(31);
p1.setName("Peter");
p1.setAddress(address);
Person p2=(Person) p1.clone();
System.out.println(p1==p2);//false
p2.getAddress().setType("Office");
System.out.println("p1="+p1);
System.out.println("p2="+p2);
}
查看输出:
false p1=Person(name=Peter, age=31, address=Address(type=Office, value=北京)) p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
遇到了点麻烦,只修改了p2的地址类型,两个地址类型都变成了Office。
浅拷贝和深拷贝
前面实例中是浅拷贝和深拷贝的典型用例。
浅拷贝:被复制对象的所有值属性都含有与原来对象的相同,而所有的对象引用属性仍然指向原来的对象。
深拷贝:在浅拷贝的基础上,所有引用其他对象的变量也进行了clone,并指向被复制过的新对象。
也就是说,一个默认的clone()方法实现机制,仍然是赋值。
如果一个被复制的属性都是基本类型,那么只需要实现当前类的cloneable机制就可以了,此为浅拷贝。
如果被复制对象的属性包含其他实体类对象引用,那么这些实体类对象都需要实现cloneable接口并覆盖clone()方法。
@Data
public class Address implements Cloneable {
private String type;
private String value;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
这样还不够,Person的clone()需要显式地clone其引用成员。
@Data
public class Person implements Cloneable {
private String name;
private Integer age;
private Address address;
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj=super.clone();
Address a=((Person)obj).getAddress();
((Person)obj).setAddress((Address) a.clone());
return obj;
}
}
重新跑前面的测试用例:
false p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京)) p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
clone方式深拷贝小结
① 如果有一个非原生成员,如自定义对象的成员,那么就需要:
- 该成员实现Cloneable接口并覆盖clone()方法,不要忘记提升为public可见。
- 同时,修改被复制类的clone()方法,增加成员的克隆逻辑。
② 如果被复制对象不是直接继承Object,中间还有其它继承层次,每一层super类都需要实现Cloneable接口并覆盖clone()方法。
与对象成员不同,继承关系中的clone不需要被复制类的clone()做多余的工作。
一句话来说,如果实现完整的深拷贝,需要被复制对象的继承链、引用链上的每一个对象都实现克隆机制。
前面的实例还可以接受,如果有N个对象成员,有M层继承关系,就会很麻烦。
利用序列化实现深拷贝
clone机制不是强类型的限制,比如实现了Cloneable并没有强制继承链上的对象也实现;也没有强制要求覆盖clone()方法。因此编码过程中比较容易忽略其中一个环节,对于复杂的项目排查就是困难了。
要寻找可靠的,简单的方法,序列化就是一种途径。
1.被复制对象的继承链、引用链上的每一个对象都实现java.io.Serializable接口。这个比较简单,不需要实现任何方法,serialVersionID的要求不强制,对深拷贝来说没毛病。
2.实现自己的deepClone方法,将this写入流,再读出来。俗称:冷冻-解冻。
@Data
public class Person implements Serializable {
private String name;
private Integer age;
private Address address;
public Person deepClone() {
Person p2=null;
Person p1=this;
PipedOutputStream out=new PipedOutputStream();
PipedInputStream in=new PipedInputStream();
try {
in.connect(out);
} catch (IOException e) {
e.printStackTrace();
}
try(ObjectOutputStream bo=new ObjectOutputStream(out);
ObjectInputStream bi=new ObjectInputStream(in);) {
bo.writeObject(p1);
p2=(Person) bi.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return p2;
}
}
原型工厂类
为了便于测试,也节省篇幅,封装一个工厂类。
公平起见,避免某些工具库使用缓存机制,使用原型方式工厂。
public class PersonFactory{
public static Person newPrototypeInstance(){
Address address = new Address();
address.setType("Home");
address.setValue("北京");
Person p1 = new Person();
p1.setAddress(address);
p1.setAge(31);
p1.setName("Peter");
return p1;
}
}
利用Dozer拷贝对象
Dozer是一个Bean处理类库。
maven依赖
<dependency> <groupId>net.sf.dozer</groupId> <artifactId>dozer</artifactId> <version>5.5.1</version> </dependency>
测试用例:
@Data
public class Person {
private String name;
private Integer age;
private Address address;
@Test
public void testDozer() {
Person p1=PersonFactory.newPrototypeInstance();
Mapper mapper = new DozerBeanMapper();
Person p2 = mapper.map(p1, Person.class);
p2.getAddress().setType("Office");
System.out.println("p1=" + p1);
System.out.println("p2=" + p2);
}
}
@Data
public class Address {
private String type;
private String value;
}
输出:
p1=Person(name=Peter, age=31, address=Address(type=Home, value=北京)) p2=Person(name=Peter, age=31, address=Address(type=Office, value=北京))
注意:在万次测试中dozer有一个很严重的问题,如果DozerBeanMapper对象在for循环中创建,效率(dozer:7358)降低近10倍。由于DozerBeanMapper是线程安全的,所以不应该每次都创建新的实例。可以自带的单例工厂DozerBeanMapperSingletonWrapper来创建mapper,或集成到spring中。
还有更暴力的,创建一个People类:
@Data
public class People {
private String name;
private String age;//这里已经不是Integer了
private Address address;
@Test
public void testDozer() {
Person p1=PersonFactory.newPrototypeInstance();
Mapper mapper = new DozerBeanMapper();
People p2 = mapper.map(p1, People.class);
p2.getAddress().setType("Office");
System.out.println("p1=" + p1);
System.out.println("p2=" + p2);
}
}
只要属性名相同,干~
继续蹂躏:
@Data
public class People {
private String name;
private String age;
private Map<String,String> address;//��
@Test
public void testDozer() {
Person p1=PersonFactory.newPrototypeInstance();
Mapper mapper = new DozerBeanMapper();
People p2 = mapper.map(p1, People.class);
p2.getAddress().put("type", "Office");
System.out.println("p1=" + p1);
System.out.println("p2=" + p2);
}
}
利用Commons-BeanUtils复制对象
maven依赖
<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.3</version> </dependency>
测试用例:
@Data
public class Person {
private String name;
private String age;
private Address address;
@Test
public void testCommonsBeanUtils(){
Person p1=PersonFactory.newPrototypeInstance();
try {
Person p2=(Person) BeanUtils.cloneBean(p1);
System.out.println("p1=" + p1);
p2.getAddress().setType("Office");
System.out.println("p2=" + p2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
利用cglib复制对象
maven依赖:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.4</version> </dependency>
测试用例:
@Test
public void testCglib(){
Person p1=PersonFactory.newPrototypeInstance();
BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, false);
Person p2=new Person();
beanCopier.copy(p1, p2,null);
p2.getAddress().setType("Office");
System.out.println("p1=" + p1);
System.out.println("p2=" + p2);
}
结果大跌眼镜,cglib这么牛x,居然是浅拷贝。不过cglib提供了扩展能力:
@Test
public void testCglib(){
Person p1=PersonFactory.newPrototypeInstance();
BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true);
Person p2=new Person();
beanCopier.copy(p1, p2, new Converter(){
@Override
public Object convert(Object value, Class target, Object context) {
if(target.isSynthetic()){
BeanCopier.create(target, target, true).copy(value, value, this);
}
return value;
}
});
p2.getAddress().setType("Office");
System.out.println("p1=" + p1);
System.out.println("p2=" + p2);
}
Orika复制对象
orika的作用不仅仅在于处理bean拷贝,更擅长各种类型之间的转换。
maven依赖:
<dependency> <groupId>ma.glasnost.orika</groupId> <artifactId>orika-core</artifactId> <version>1.5.0</version> </dependency> </dependencies>
测试用例:
@Test
public void testOrika() {
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(Person.class, Person.class)
.byDefault()
.register();
ConverterFactory converterFactory = mapperFactory.getConverterFactory();
MapperFacade mapper = mapperFactory.getMapperFacade();
Person p1=PersonFactory.newPrototypeInstance();
Person p2 = mapper.map(p1, Person.class);
System.out.println("p1=" + p1);
p2.getAddress().setType("Office");
System.out.println("p2=" + p2);
}
Spring BeanUtils复制对象
给Spring个面子,貌似它不支持深拷贝。
Person p1=PersonFactory.newPrototypeInstance(); Person p2 = new Person(); Person p2 = (Person) BeanUtils.cloneBean(p1); //BeanUtils.copyProperties(p2, p1);//这个更没戏
深拷贝性能对比
@Test
public void testBatchDozer(){
Long start=System.currentTimeMillis();
Mapper mapper = new DozerBeanMapper();
for(int i=0;i<10000;i++){
Person p1=PersonFactory.newPrototypeInstance();
Person p2 = mapper.map(p1, Person.class);
}
System.out.println("dozer:"+(System.currentTimeMillis()-start));
//dozer:721
}
@Test
public void testBatchBeanUtils(){
Long start=System.currentTimeMillis();
for(int i=0;i<10000;i++){
Person p1=PersonFactory.newPrototypeInstance();
try {
Person p2=(Person) BeanUtils.cloneBean(p1);
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("commons-beanutils:"+(System.currentTimeMillis()-start));
//commons-beanutils:229
}
@Test
public void testBatchCglib(){
Long start=System.currentTimeMillis();
for(int i=0;i<10000;i++){
Person p1=PersonFactory.newPrototypeInstance();
BeanCopier beanCopier=BeanCopier.create(Person.class, Person.class, true);
Person p2=new Person();
beanCopier.copy(p1, p2, new Converter(){
@Override
public Object convert(Object value, Class target, Object context) {
if(target.isSynthetic()){
BeanCopier.create(target, target, true).copy(value, value, this);
}
return value;
}
});
}
System.out.println("cglib:"+(System.currentTimeMillis()-start));
//cglib:133
}
@Test
public void testBatchSerial(){
Long start=System.currentTimeMillis();
for(int i=0;i<10000;i++){
Person p1=PersonFactory.newPrototypeInstance();
Person p2=p1.deepClone();
}
System.out.println("serializable:"+(System.currentTimeMillis()-start));
//serializable:687
}
@Test
public void testBatchOrika() {
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(Person.class, Person.class)
.field("name", "name")
.byDefault()
.register();
ConverterFactory converterFactory = mapperFactory.getConverterFactory();
MapperFacade mapper = mapperFactory.getMapperFacade();
Long start=System.currentTimeMillis();
for(int i=0;i<10000;i++){
Person p1=PersonFactory.newPrototypeInstance();
Person p2 = mapper.map(p1, Person.class);
}
System.out.println("orika:"+(System.currentTimeMillis()-start));
//orika:83
}
@Test
public void testBatchClone(){
Long start=System.currentTimeMillis();
for(int i=0;i<10000;i++){
Person p1=PersonFactory.newPrototypeInstance();
try {
Person p2=(Person) p1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
System.out.println("clone:"+(System.currentTimeMillis()-start));
//clone:8
}
(10k)性能比较:
//dozer:721 //commons-beanutils:229 //cglib:133 //serializable:687 //orika:83 //clone:8
深拷贝总结
原生的clone效率无疑是最高的,用脚趾头都能想到。
偶尔用一次,用哪个都问题都不大。
一般性能要求稍高的应用场景,cglib和orika完全可以接受。
另外一个考虑的因素,如果项目已经引入了某个依赖,就用那个依赖来做吧,没必要再引入一个第三方依赖。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
# java对象拷贝
# java对象拷贝详解
# java
# 深拷贝
# java对象拷贝常见面试题及应答汇总
# Java 如何优雅的拷贝对象属性
# Java 对象深拷贝工具类的实现
# java如何拷贝复制对象和集合问题
# Java对象进行深拷贝的五种方法实例代码
# 北京
# 不需要
# 创建一个
# 有一个
# 自己的
# 实体类
# 都是
# 是一个
# 也就是说
# 实现了
# 他们的
# 就会
# 来了
# 序列化
# 和深
# 有个
# 还可以
# 基础上
# 都能
# 这是一个
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Swift中switch语句区间和元组模式匹配
微信推文制作网站有哪些,怎么做微信推文,急?
Laravel怎么实现模型属性转换Casting_Laravel自动将JSON字段转为数组【技巧】
Windows家庭版如何开启组策略(gpedit.msc)?(安装方法)
高端智能建站公司优选:品牌定制与SEO优化一站式服务
JS中对数组元素进行增删改移的方法总结
Laravel怎么上传文件_Laravel图片上传及存储配置
Laravel怎么防止CSRF攻击_Laravel CSRF保护中间件原理与实践
EditPlus中的正则表达式实战(5)
教你用AI润色文章,让你的文字表达更专业
Laravel怎么创建自己的包(Package)_Laravel扩展包开发入门到发布
详解MySQL数据库的安装与密码配置
高端云建站费用究竟需要多少预算?
Claude怎样写约束型提示词_Claude约束提示词写法【教程】
为什么php本地部署后css不生效_静态资源加载失败修复技巧【技巧】
佛山企业网站制作公司有哪些,沟通100网上服务官网?
Laravel如何集成微信支付SDK_Laravel使用yansongda-pay实现扫码支付【实战】
Laravel如何处理异常和错误?(Handler示例)
Laravel怎么实现一对多关联查询_Laravel Eloquent模型关系定义与预加载【实战】
Edge浏览器怎么启用睡眠标签页_节省电脑内存占用优化技巧
如何在宝塔面板创建新站点?
如何挑选优质建站一级代理提升网站排名?
如何快速生成橙子建站落地页链接?
如何在七牛云存储上搭建网站并设置自定义域名?
安克发布新款氮化镓充电宝:体积缩小 30%,支持 200W 输出
浅谈javascript alert和confirm的美化
猎豹浏览器开发者工具怎么打开 猎豹浏览器F12调试工具使用【前端必备】
在线教育网站制作平台,山西立德教育官网?
制作电商网页,电商供应链怎么做?
网站制作报价单模板图片,小松挖机官方网站报价?
在线ppt制作网站有哪些软件,如何把网页的内容做成ppt?
Laravel如何与Docker(Sail)协同开发?(环境搭建教程)
EditPlus中的正则表达式实战(6)
Android GridView 滑动条设置一直显示状态(推荐)
原生JS获取元素集合的子元素宽度实例
矢量图网站制作软件,用千图网的一张矢量图做公司app首页,该网站并未说明版权等问题,这样做算不算侵权?应该如何解决?
Laravel如何安装使用Debugbar工具栏_Laravel性能调试与SQL监控插件【步骤】
Laravel Pest测试框架怎么用_从PHPUnit转向Pest的Laravel测试教程
Laravel Eloquent访问器与修改器是什么_Laravel Accessors & Mutators数据处理技巧
图片制作网站免费软件,有没有免费的网站或软件可以将图片批量转为A4大小的pdf?
Laravel如何设置定时任务(Cron Job)_Laravel调度器与任务计划配置
如何在 Python 中将列表项按字母顺序编号(a.、b.、c. …)
Laravel如何实现用户角色和权限系统_Laravel角色权限管理机制
Laravel Debugbar怎么安装_Laravel调试工具栏配置指南
微信公众帐号开发教程之图文消息全攻略
Laravel如何升级到最新的版本_Laravel版本升级流程与兼容性处理
Laravel如何生成URL和重定向?(路由助手函数)
Java类加载基本过程详细介绍
韩国网站服务器搭建指南:VPS选购、域名解析与DNS配置推荐
Laravel怎么配置不同环境的数据库_Laravel本地测试与生产环境动态切换【方法】

