简单实用的对象转换复制工具
一、概述
工作中经常会遇到这样的场景,需要把对象A中的变量复制到对象B中,这是一个枯燥又没有技术含量的工作,最繁杂枯燥的方法是先调用A对象的get方法将A中待复制的变量取出然后再调用B对象的set方法将对应的变量set到B对象中得到结果。后来有了BeanUtils提供的BeanUtils.copyProperties()方法可以简单快速的复制对象,但是新的问题又来了:如果对象A中的变量名和对象B中的变量名不同怎么办?就好比我们需要将对象A中的Integer a复制到对象B中的Integer b中,这时BeanUtils就无法派上用场了。所以为了使对象的复制更具普适性,我提供了一个对象转换复制的工具。
二、条件
我们先将概述中的需求用代码体现出来,由于是测试类,所以写的就比较随意了。
首先我们随便定一个类Temp:
@Data class Temp { String s; }
然后我们有一个待转换类Source:
@Data class Source { String a; Integer b; Temp sourceTemp; String[] d; String[] sourceE; String[] sourceF; }
最后我们要有一个结果类Target:
@Data class Target { Integer a; String b; Temp targetTemp; String[] d; String[] targetE; }
可以看出Source类和Target类里有的变量名相同有的变量名不同,我们想要做的就是要忽略掉变量sourceF,不复制d,并且将sourceTemp复制到targetTemp,sourceE复制到targetE中,然后得到Target类。
三、实现
看到这个场景,我第一反应就是注解,通过在Source上添加注解指定对应的Target,所以我第一步就是创建一个TransTarget注解来指定目标对象的变量名。
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface TransTarget { String value(); //目标对象变量名 }
有了思路之后接下来自然就是如何使用这个注解的问题。我的想法是创建一个方法,将Source对象和Target的class作为入参传入,使用反射拿到Source的Field列表,然后遍历列表检查是否有TransTarget注解标注,如果有,我就通过反射去调用Target中对应的set方法;如果没有,我就通过翻着调用Target中和Source相同变量名的变量的set方法。以下是具体实现。
public static <K, V> V transBean(K k, Class<V> vClass) throws IllegalAccessException, InvocationTargetException { Class<?> kClass = k.getClass(); List<Field> fieldList = Arrays.asList(kClass.getDeclaredFields()); //循环待转换变量,通过反射一一转换得到结果。 V v = null; try { v = vClass.getDeclaredConstructor().newInstance(); } catch (InstantiationException e) { throw new ObjectTransException("无法实例化抽象类或接口。", e); } catch (NoSuchMethodException e) { throw new ObjectTransException(vClass.getName() + "无默认构造方法。", e); } for (Field field : fieldList) { String name = field.getName(); TransTarget transTarget = field.getAnnotation(TransTarget.class); String s = transTarget != null ? transTarget.value() : name; Field targetField = null; try { targetField = vClass.getDeclaredField(s); } catch (NoSuchFieldException e) { continue; } if (field.getType().equals(targetField.getType())) { String sourceMethodName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1); String targetMethodName = "set" + s.substring(0, 1).toUpperCase() + s.substring(1); Method sourceMethod = null; Method targetMethod = null; try { sourceMethod = kClass.getDeclaredMethod(sourceMethodName); targetMethod = vClass.getDeclaredMethod(targetMethodName, field.getType()); } catch (NoSuchMethodException e) { throw new ObjectTransException("无法获取到对应方法。", e); } Object invoke = sourceMethod.invoke(k); targetMethod.invoke(v, invoke); } } return v; }
这里我只处理了部分异常,其他的就由调用者自行处理好了。这样就基本实现了我们需要的功能,接下来我们还需要忽略指定的变量d。这里我采取的方法还是使用注解,创建一个注解IgnoreFields,里面用一个字符串来标记哪些变量不需要复制。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface IgnoreFields { String[] value() default {}; }
然后在transBean方法中对应的地方加上判断语句
if (kClass.isAnnotationPresent(IgnoreFields.class)) { IgnoreFields ignoreFields = kClass.getAnnotation(IgnoreFields.class); List<String> ignoreFiledList = Arrays.asList(ignoreFields.value()); if (CollectionUtils.isNotEmpty(ignoreFiledList)) { fieldList = fieldList.stream() .filter(field -> !ignoreFiledList.contains(field.getName())) .collect(Collectors.toList()); } }
转换工具就大功告成了,下面我们需要改造一下Source类。
@Data @IgnoreFields("d") class Source { String a; Integer b; @TransTarget("targetTemp") Temp sourceTemp; String[] d; @TransTarget("targetE") String[] sourceE; String[] sourceF; }
最后我们来测试一下效果如何:
public static void main(String[] args) throws InvocationTargetException, NoSuchFieldException, IllegalAccessException { Temp temp = new Temp(); temp.setS("testTemp"); Source source = new Source(); source.setA("a"); source.setB(1); source.setSourceTemp(temp); String[] test1s = {"d", "d"}; String[] test2s = {"e", "e"}; String[] test3s = {"f", "f"}; source.setD(test1s); source.setSourceE(test2s); source.setSourceF(test3s); Target target = ObjectTransUtil.transBean(source, Target.class); System.out.println(target); }
最后得到的结果是:
可见Source中对应的字段都复制到了Target中,字段名不同的也成功复制,想要忽略的也都忽略掉了,并且使用也很方便,大功告成。
当然,这些代码还有很大的优化空间,希望看到这篇文章的朋友可以提出宝贵的意见,大家一起交流提升。
------------------------------------------------------------------------
欢迎关注我的个人公众号,推送最新文章

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
BigData NoSQL —— ApsaraDB HBase数据存储与分析平台概览
摘要:数据库发展有三个明显的趋势:1. 越来越多的数据库会做云原生(CloudNative);2. NoSQL正在解决BigData领域的问题;3. 越来越多的公司或者产品都是融合多个能力。 阿里云HBase经过公共云两年(单独的HBase在阿里内部已经发展快9年)的发展,融合开源Apache HBase、Apache Phoenix、Apache Spark、Apache Solr等开源项目,再加上一系列自研特性,满足 【一体化数据处理平台,提供一站式能力】。 一、引言 时间到了2019年,数据库也发展到了一个新的拐点,有三个明显的趋势: 越来越多的数据库会做云原生(CloudNative),会不断利用新的硬件及云本身的优势打造CloudNative数据库,国内以阿里云的Cloud HBase、POLARDB为代表,此块文章会有一定的引述,但不是本文的重点。 NoSQL正在解决BigData领域的问题。根据Forrester NoSQL的报告,BigData NoSQL是提供 存储、计算处理、支持水平扩展、Schemaless以及灵活的数据模型,特别提到需要支持复杂计算,一般通过集成...
- 下一篇
HeyUI组件库按需加载功能上线,盘点HeyUI组件库有哪些独特功能?
HeyUI组件库 如果你还不了解heyui组件库,欢迎来我们的官网或者github参观。 官网 github 当然,如果能给我们一颗✨✨✨,那是最赞的了! 按需加载 当heyui组件库的组件越来越多的时候,按需加载的功能终于上线了。 话不多说,先把按需加载的使用方式放出来。 在线示例 按需加载在线示例 以此图为例,按需加载后,js与css的大小将大幅度减小。 示例代码 import Vue from 'vue'; import App from './app.vue'; import { install, Prototypes, Button, DropdownMenu } from 'heyui'; require('../css/module.less'); Vue.use(install, { components: { Button, DropdownMenu }, prototypes: Prototypes }); const app = new Vue({ el: '#app', render: h => h(App) }); exp...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS8安装Docker,最新的服务器搭配容器使用
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题