谈谈Java开发中的对象拷贝
在Java开发工作中,有很多时候我们需要将不同的两个对象实例进行属性复制,从而基于源对象的属性信息进行后续操作,而不改变源对象的属性信息。这两个对象实例有可能是同一个类的两个实例,也可能是不同类的两个实例,但是他们的属相名称相同。例如DO、DTO、VO、DAO等,这些实体的意义请查看DDD中分层架构。本文主要介绍几种对象拷贝的方法
1. 对象拷贝
对象拷贝分为深拷贝和浅拷贝。根据使用场景进行不同选择。在Java中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。
深度拷贝和浅度拷贝的主要区别在于是否支持引用类型的属性拷贝,本文将探讨目前使用较多的几种对象拷贝的方案,以及其是否支持深拷贝和性能对比。
2. BeanUtils
2.1 apache的BeanUtils方案
使用org.apache.commons.beanutils.BeanUtils进行对象深入复制时候,主要通过向BeanUtils框架注入新的类型转换器,因为默认情况下,BeanUtils对复杂对象的复制是引用,例如:
public static void beanUtilsTest() throws Exception {// 注册转化器BeanUtilsBean.getInstance().getConvertUtils().register(new ArbitrationConvert(), ArbitrationDO.class);Wrapper wrapper = new Wrapper();wrapper.setName("copy");wrapper.setNameDesc("copy complex object!");wrapper.setArbitration(newArbitrationDO());Wrapper dest = new Wrapper();// 对象复制BeanUtils.copyProperties(dest, wrapper);// 属性验证wrapper.getArbitration().setBizId("1");System.out.println(wrapper.getArbitration() == dest.getArbitration());System.out.println(wrapper.getArbitration().getBizId().equals(dest.getArbitration().getBizId()));
}public class ArbitrationConvert implements Converter {@Overridepublic <T> T convert(Class<T> type, Object value) {if (ArbitrationDO.class.equals(type)) {try {return type.cast(BeanUtils.cloneBean(value));} catch (Exception e) {e.printStackTrace();}}return null;}
}
可以发现,使用org.apache.commons.beanutils.BeanUtils复制引用时,主和源的引用为同一个,即改变了主的引用属性会影响到源的引用,所以这是一种浅拷贝。
需要注意的是,apache的BeanUtils中,以下类型如果为空,会报错(org.apache.commons.beanutils.ConversionException: No value specified for *)
/*** Register the converters for other types.* </p>* This method registers the following converters:* <ul>* <li>Class.class - {@link ClassConverter}* <li>java.util.Date.class - {@link DateConverter}* <li>java.util.Calendar.class - {@link CalendarConverter}* <li>File.class - {@link FileConverter}* <li>java.sql.Date.class - {@link SqlDateConverter}* <li>java.sql.Time.class - {@link SqlTimeConverter}* <li>java.sql.Timestamp.class - {@link SqlTimestampConverter}* <li>URL.class - {@link URLConverter}* </ul>* @param throwException <code>true if the converters should* throw an exception when a conversion error occurs, otherwise <code>* <code>false if a default value should be used.*/private void registerOther(boolean throwException) {register(Class.class, throwException ? new ClassConverter() : new ClassConverter(null));register(java.util.Date.class, throwException ? new DateConverter() : new DateConverter(null));register(Calendar.class, throwException ? new CalendarConverter() : new CalendarConverter(null));register(File.class, throwException ? new FileConverter() : new FileConverter(null));register(java.sql.Date.class, throwException ? new SqlDateConverter() : new SqlDateConverter(null));register(java.sql.Time.class, throwException ? new SqlTimeConverter() : new SqlTimeConverter(null));register(Timestamp.class, throwException ? new SqlTimestampConverter() : new SqlTimestampConverter(null));register(URL.class, throwException ? new URLConverter() : new URLConverter(null));}
当遇到这种问题是,可以手动将类型转换器注册进去,比如data类型:
public class BeanUtilEx extends BeanUtils { private static Map cache = new HashMap();
private static Log logger = LogFactory.getFactory().getInstance(BeanUtilEx.class); private BeanUtilEx() {
} static {
// 注册sql.date的转换器,即允许BeanUtils.copyProperties时的源目标的sql类型的值允许为空
ConvertUtils.register(new org.apache.commons.beanutils.converters.SqlDateConverter(null), java.sql.Date.class);
ConvertUtils.register(new org.apache.commons.beanutils.converters.SqlDateConverter(null), java.util.Date.class);
ConvertUtils.register(new org.apache.commons.beanutils.converters.SqlTimestampConverter(null), java.sql.Timestamp.class);
// 注册util.date的转换器,即允许BeanUtils.copyProperties时的源目标的util类型的值允许为空
} public static void copyProperties(Object target, Object source)
throws InvocationTargetException, IllegalAccessException {
// 支持对日期copy
org.apache.commons.beanutils.BeanUtils.copyProperties(target, source); }
2.2 apache的PropertyUtils方案
PropertyUtils的copyProperties()方法几乎与BeanUtils.copyProperties()相同,主要的区别在于后者提供类型转换功能,即发现两个JavaBean的同名属性为不同类型时,在支持的数据类型范围内进行转换,PropertyUtils不支持这个功能,所以说BeanUtils使用更普遍一点,犯错的风险更低一点。而且它仍然属于浅拷贝。
Apache提供了 SerializationUtils.clone(T),T对象需要实现 Serializable 接口,他属于深克隆。
2.3 spring的BeanUtils方案
Spring中的BeanUtils,其中实现的方式很简单,就是对两个对象中相同名字的属性进行简单get/set,仅检查属性的可访问性。
public static void copyProperties(Object source, Object target) throws BeansException {copyProperties(source, target, (Class)null, (String[])null);}public static void copyProperties(Object source, Object target, Class<?> editable) throws BeansException {copyProperties(source, target, editable, (String[])null);}public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException {copyProperties(source, target, (Class)null, ignoreProperties);}private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties) throws BeansException {Assert.notNull(source, "Source must not be null");Assert.notNull(target, "Target must not be null");Class actualEditable = target.getClass();if(editable != null) {if(!editable.isInstance(target)) {throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]");}actualEditable = editable;}PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);List ignoreList = ignoreProperties != null?Arrays.asList(ignoreProperties):null;PropertyDescriptor[] var7 = targetPds;int var8 = targetPds.length;for(int var9 = 0; var9 < var8; ++var9) {PropertyDescriptor targetPd = var7[var9];Method writeMethod = targetPd.getWriteMethod();if(writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());if(sourcePd != null) {Method readMethod = sourcePd.getReadMethod();if(readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {try {if(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {readMethod.setAccessible(true);}Object ex = readMethod.invoke(source, new Object[0]);if(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {writeMethod.setAccessible(true);}writeMethod.invoke(target, new Object[]{ex});} catch (Throwable var15) {throw new FatalBeanException("Could not copy property \'" + targetPd.getName() + "\' from source to target", var15);}}}}}}
可以看到, 成员变量赋值是基于目标对象的成员列表, 并且会跳过ignore的以及在源对象中不存在的, 所以这个方法是安全的, 不会因为两个对象之间的结构差异导致错误, 但是必须保证同名的两个成员变量类型相同.
3. dozer
Dozer(http://dozer.sourceforge.net/)能够实现深拷贝。Dozer是基于反射来实现对象拷贝,反射调用set/get 或者是直接对成员变量赋值 。 该方式通过invoke执行赋值,实现时一般会采用beanutil, Javassist等开源库。
简单引用网上的例子,大多都是基于xml的配置,具体请查看其它Blog:
package com.maven.demo;import java.util.HashMap;
import java.util.Map;import org.dozer.DozerBeanMapper;
import org.junit.Test;import static org.junit.Assert.assertEquals;public class Demo{/*** map->bean*/@Testpublic void testDozer1() {Map<String,Object> map = new HashMap();map.put("id", 10000L);map.put("name", "小兵");map.put("description", "帅气逼人");DozerBeanMapper mapper = new DozerBeanMapper();ProductVO product = mapper.map(map, ProductVO.class);assertEquals("小兵",product.getName());assertEquals("帅气逼人",product.getDescription());assertEquals(Long.valueOf("10000"), product.getId());}/*** VO --> Entity (不同的实体之间,不同的属性字段进行复制)*/@Testpublic void testDozer2(){ProductVO product = new ProductVO();product.setId(10001L);product.setName("xiaobing");product.setDescription("酷毙了");DozerBeanMapper mapper = new DozerBeanMapper();ProductEntity productEntity = mapper.map(product, ProductEntity.class);assertEquals("xiaobing",productEntity.getProductName());}}
4. MapStrcut
MapStrcut属于编译期的对象复制方案,它能够动态生成set/get代码的class文件 ,在运行时直接调用该class文件。该方式实际上扔会存在set/get代码,只是不需要自己写了。
@Mapper(componentModel = "spring")
public interface MonitorAppGroupIdcDTOMapper {MonitorAppGroupIdcDTOMapper MAPPER = Mappers.getMapper(MonitorAppGroupIdcDTOMapper.class);void mapping(MonitorAppGroupIdcDTO source, @MappingTarget MonitorAppGroupIdcDTO dest);
}
5. 自定义Pojoconvert
public J copyPojo( P src, J des) throws NoSuchMethodException,SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {if(src == null || des==null){return null;}String name = null ;String sufix = null;Class<?> cls = des.getClass() ;Method[] methods = cls.getMethods();for(Method m: methods){name = m.getName();if(name!=null && name.startsWith("set") && m.getParameterTypes().length==1){sufix = name.substring(3);m.getParameterTypes() ;Method getM = cls.getMethod("get"+sufix);m.invoke(des, getM.invoke(src));}}return des ;
}
没有那么多验证,不是很安全但是性能不错。
6. BeanCopier
@Testpublic void test_convert_entity_to_model_performance_use_beancopier(){List<ShopCouponEntity> entityList = ...long start = System.currentTimeMillis();BeanCopier b = BeanCopier.create(ShopCouponEntity.class, ShopCouponModel.class, false);List<ShopCouponModel> modelList = new ArrayList<>();for (ShopCouponEntity src : entityList) {ShopCouponModel dest = new ShopCouponModel();b.copy(src, dest, null);modelList.add(dest);}System.out.printf("BeanCopier took time: %d(ms)%n",System.currentTimeMillis() - start);}
可以通过缓存BeanCopier的实例来提高性能。
BeanCopier b = getFromCache(sourceClass,targetClass); //从缓存中取long start = System.currentTimeMillis();List<ShopCouponModel> modelList = new ArrayList<>();for (ShopCouponEntity src : entityList) {ShopCouponModel dest = new ShopCouponModel();b.copy(src, dest, null);modelList.add(dest);}
7. fastjson和GSON
使用fastjson和GSON主要是通过对象json序列化和反序列化来完成对象复制,这里只是提供一种不一样的对象拷贝的思路,例子略。
8. 性能
对两种BeanUtils、Gson以及自定义Pojoconvert测试了性能
NewNovelMode des = null ;
NewNovelMode ori = buildModel();
Gson gson = new Gson();
int count = 100000;
//org.springframework.beans.BeanUtils.copyProperties
long s = System.currentTimeMillis();
for(int i=0;i<count;i++){des = new NewNovelMode();org.springframework.beans.BeanUtils.copyProperties(ori, des);
}
System.out.println("springframework BeanUtils cost:"+(System.currentTimeMillis() - s));
// System.out.println(new Gson().toJson(des));//org.apache.commons.beanutils.BeanUtils
s = System.currentTimeMillis();
for(int i=0;i<count;i++){des = new NewNovelMode();org.apache.commons.beanutils.BeanUtils.copyProperties(des, ori);
}
System.out.println("apache BeanUtils cost:"+(System.currentTimeMillis() - s));
// System.out.println(new Gson().toJson(des));//gson转换
s = System.currentTimeMillis();
for(int i=0;i<count;i++){des = gson.fromJson(gson.toJson(ori), NewNovelMode.class);
}
System.out.println("gson cost:"+(System.currentTimeMillis() - s));
// System.out.println(new Gson().toJson(des));//Pojo转换类
s = System.currentTimeMillis();
PojoUtils<NewNovelMode, NewNovelMode> pojoUtils = new PojoUtils<NewNovelMode, NewNovelMode>();
for(int i=0;i<count;i++){des = new NewNovelMode();pojoUtils.copyPojo(ori,des);
}
System.out.println("Pojoconvert cost:"+(System.currentTimeMillis() - s));
// System.out.println(new Gson().toJson(des));
结果就不贴出来了,在这里总结一下
Spring的BeanUtils比较稳定,不会因为量大了,耗时明显增加,但其实基准耗时比较长;apache的BeanUtils稳定性与效率都不行,不可取;Gson,因为做两个gson转换,所以正常项目中,可能耗时会更少一些;PojoUtils稳定不如spring,但是总耗时优势明显,原因是它只是根据项目的需求,实现的简单的转换模板,这个代码在其它的几个工具类均有。
而在网上的其他Blog中(参见Reference),对Apache的BeanUtils、PropertyUtils和CGLIB的BeanCopier作了性能测试。
测试结果:
性能对比: BeanCopier > BeanUtils. 其中BeanCopier的性能高出另外两个100数量级。
综上推荐使用:
1. BeanUtils(简单,易用)
2. BeanCopier(加入缓存后和手工set的性能接近)
3. Dozer(深拷贝)
4. fastjson(特定场景下使用)
转自:https://my.oschina.net/hosee/blog/1483965
谈谈Java开发中的对象拷贝相关推荐
- 《Android游戏开发详解》一2.18 使用Java API中的对象
本节书摘来异步社区<Android游戏开发详解>一书中的第2章,第2.18节,译者: 李强 责编: 陈冀康,更多章节内容可以访问云栖社区"异步社区"公众号查看. 2.1 ...
- Java开发中常见的危险信号
Dustin Marx是一位专业软件开发者,从业已经有17年的时间,他拥有电子工程学士学位,还是一位MBA.Dustin维护着一个博客,专门介绍软件开发的各个主题.近日,他撰文谈到了Java开发中常见 ...
- 编写高质量代码:改善Java程序的151个建议 (第1章 Java开发中通用的方法和准则)
第1章 Java开发中通用的方法和准则 The reasonable man adapts himself to the world;the unreasonable one persists in ...
- 谈谈WEB开发中的苦大难字符集问题
记得刚做javaweb开发的时候被这个编码问题搞得晕头转向,经常稀里糊涂的编码正常了一会编码又乱了.那个时候迫于项目进度大多都是知其然不知其所以然.后来有时间就把整个体系搞了个遍,终于摸通了来龙去脉. ...
- Java开发中文件读取方式总结
JAVA开发中,免不了要读文件操作,读取文件,首先就需要获取文件的路径.路径分为绝对路径和相对路径. 在文件系统中,绝对路径都是以盘符开始的,例如C:abc1.txt. 什么是相对路径呢?相对路径就是 ...
- Java开发中更多常见的危险信号
在< Java开发中的常见危险信号>一文中,我研究了一些不一定本身就是错误或不正确的做法,但它们可能表明存在更大的问题. 这些"红色标记"类似于"代码气味&q ...
- Java开发中的常见危险信号
在开发,阅读,复审和维护成千上万行Java代码的几年中,我已经习惯于看到Java代码中的某些" 危险信号 ",这些信号通常(但可能并非总是)暗示着代码问题. 我不是在谈论总是错误的 ...
- Java开发中常用的设计模式-单例模式
单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式. Java开发中常用的设计模式-单例模式 单例模式有3个特点: 单例类只有一个实例对象: 该单例对象必须 ...
- Javascript中的对象拷贝(对象复制/克隆)
Javascript中的对象拷贝(对象复制/克隆) 李俊才 CSDN:jcLee95 邮箱:291148484@163.com 1. 对象的引用 要说"拷贝"还要先说"引 ...
最新文章
- 记录遇到的Altium designer显示布线未完成坑
- 实战 | 利用Delta Lake使Spark SQL支持跨表CRUD操作
- mysql的order by,group by和distinct优化
- python 比较文件不同,在python中逐行比较两个不同的文件
- 利用javascript动态创建表格
- 通过修改word文件,来屏蔽宏代码
- 线性调频信号的时频域分析
- 服务器没有D盘怎么架设传奇?
- 【Python】使用python 画出一张机器猫doraemon
- linux asm 裸设备,为ASM生成裸设备
- Windows防火墙的应用
- git 调换提交顺序
- 怎样调整Firefox火狐浏览器开发者控制台字体大小
- 拆长虹iho3000_(CA版)四川长虹iho-3000t晶晨s905l-b刷全网通系统教程可救砖头
- 机器学习项目实战----新闻分类任务(二)
- MySQL趋势与前景技术分享
- python可视化神器——pyecharts(词云图雷达图极坐标系)
- Spark项目之电商用户行为分析大数据平台之(三)大数据集群的搭建
- Unity 绿幕视频抠图算法原理与实现 -- 效果极好
- dataworks函数
热门文章
- cad在线转低版本_为什么别人制图那么快?41个CAD实用技巧,3天轻松玩转CAD
- 微软的平板电脑_Microsoft 微软 Surface Go 2 10.5英寸二合一平板电脑(m3-8100Y、8GB、128GB、LTE) 5788元...
- 非阻IO与EWOULDBLOCK EAGAIN
- python钻石数据分析_数据分析该用什么工具?
- 【转】PF_RING开发指南
- 【转】UML基础: 第 2 部分 - 对象图 (Object Diagram)
- 第六节:WebApi的部署方式(自托管)
- 【转】DevOps到底是什么意思?
- oracle临时表空间组,证明临时表空间组在并发session时的作用
- python文本去重函数_python3.4.3下逐行读入txt文本并去重的方法