复制两个不同类型对象的相同字段的值

来源:互联网 发布:加强网络舆情监督工作 编辑:程序博客网 时间:2024/05/16 13:00

需求的来源是项目。

每次做项目,都要建立很多的数据体,有的是数据库表产生的ORM,有的是网络请求的参数体,有的是网络接口调用给的返回数据返回数据体。同一种数据,这几种数据具体的结构并不一样。

ORM往往跟数据表一致,并且做了映射。但是外键数据只有id,并不详细。

前端调用的数据往往要求比ORM更丰富,有些则不需要。虽说可以建立一些与ORM无关的字段来用,但是字段多了会很麻烦,所以需要建立专用的VO数据类。

接口请求的有些数据,比ORM的数据要少一些,并不需要前端传值,但是我们使用swagger做ApiDoc,不详细说明,前端也搞不清哪些必填,哪些可以不用,就连自己有时候也会搞乱,所以还是另外给前端建立一个request数据参数类比较合适。

但是这样一来,同一套数据就要在不同的数据对象之间读写不停,字段繁多的数据读写代码要写好多,看起来总觉得不舒服。

今天躺床上,一觉醒来,突然产生一种灵感:完全可以用反射来封装一个工具类,让程序自动判断匹配的字段,自动传值,不同的字段在做特殊处理,这样就会节省很多的代码。

说干就干,翻身起床,打开电脑,写一个demo。一会儿就写好了,调试通过。


一、建立一个实体类,模拟ORM插件生成那种,字段可以多一点。

@Entity(name = "tb_user")public class UserEntity {    private int id;    private String name;    private String password;    private Integer age;    private String address;    ... ...}

二、创建一个用于请求类,两个类之间的字段有交集

public class UserRequest {    private String name;    private String password;    private String vcode;        ......}
三、开始写工具类EntityUtils.java,有详细的注释

public class EntityUtils {    /**     * 复制名称相同类型相同字段的值     *     * @param obj     * @param clazz2     * @param <T1>     * @param <T2>     * @return     */    public static <T1, T2> T2 copyData(T1 obj, Class<T2> clazz2) {        //1. 获取源数据的类        Class clazz1 = obj.getClass();//源数据类        //2. 创建一个目标数据实例        final T2 obj2 = getInstance(clazz2);        //3. 获取clazz1和clazz2中的属性        Field[] fields1 = clazz1.getDeclaredFields();        Field[] fields2 = clazz2.getDeclaredFields();        //4. 遍历fields2        for (Field f1 : fields1) {            //4-1. 遍历fields1,逐字段匹配            for (Field f2 : fields2) {                // 复制字段                copyField(obj, obj2, f1, f2);            }        }        return obj2;    }    /**     * 按照字段表复制相同名称相同类型的字段的值     *     * @param obj     * @param clazz2     * @param fieldNames     * @param <T1>     * @param <T2>     * @return     */    public static <T1, T2> T2 copyData(T1 obj, Class<T2> clazz2, String[] fieldNames) {        //1. 获取源数据的类        Class clazz1 = obj.getClass();//源数据类        //2. 创建一个目标数据实例        final T2 obj2 = getInstance(clazz2);        //3. 获取clazz1和clazz2中的属性        Field[] fields1 = clazz1.getDeclaredFields();        Field[] fields2 = clazz2.getDeclaredFields();        //4. 遍历字段列表        for (String fieldName : fieldNames) {            //5. 遍历fields1            for (Field f1 : fields1) {                //找到这个字段(找不到就不用遍历fields2)                if (fieldName.equals(f1.getName())) {                    //5-1. 遍历fields2,逐字段匹配                    for (Field f2 : fields2) {                        //在fields2中也要有这个字段                        if (fieldName.equals(f2.getName())) {                            //复制字段                            copyField(obj, obj2, f1, f2);                        }                    }                }            }        }        return obj2;    }    /**     * 复制相同名称相同类型的字段的值     *     * @param obj     * @param obj2     * @param f1     * @param f2     * @param <T1>     * @param <T2>     */    private static <T1, T2> void copyField(T1 obj, T2 obj2, Field f1, Field f2) {        try {            //字段名要相同,字段类型也要相同            if (f1.getName().equals(f2.getName())                    & f1.getType().getName().equals(f2.getType().getName())) {                System.out.println(f1.getName());                //3-2. 获取obj这个字段的值                f1.setAccessible(true);                Object val = f1.get(obj);                //3-3. 把这个值赋给obj2这个字段                f2.setAccessible(true);                f2.set(obj2, val);                //3-4. 访问权限还原                f2.setAccessible(false);                f1.setAccessible(false);            }        } catch (IllegalAccessException e) {            e.printStackTrace();        }    }    /**     * 获得泛型类的实例     *     * @param tClass     * @param <T>     * @return     */    public static <T> T getInstance(Class<T> tClass) {        try {            T t = tClass.newInstance();            return t;        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }        return null;    }}

我喜欢使用的时候干干净净,所以习惯把异常在封装的时候解决掉,不喜欢抛出去。除非项目结构做了异常异常统一处理,需要throw.

四、写一个测试方法,来测试一下。

    @Test    public void test() {        UserRequest userRequest = new UserRequest();        userRequest.setName("kalychen");        userRequest.setPassword("gk123456");        userRequest.setVcode("265847");        String[] fields = {"password"};        UserEntity userEntity = EntityUtils.copyData(userRequest, UserEntity.class, fields);        System.out.println(userEntity.getName());        System.out.println(userEntity.getPassword());    }

测试结果:



这测试的是带有字段列表的,可以看出没有在字段名称数组中出现的字段,即使是名称相同类型相同也不会复制。设计这样一个重载方法目的是方便控制。本来计划中还有一个使用注解来控制的重载,但是觉得未必会比前面两个更方便,所以放弃了。

今天是周末,感觉星期一我的项目代码又要删减好多了。繁琐的代码我看着会觉得很不舒服的。

补充一下,这些实体类都是没有继承的。

迭代了一下,对程序进行了优化和扩展

public class EntityUtils {    /**     * 复制名称相同类型相同的字段数据     *     * @param sourceObj     * @param clazz     * @param <T1>     * @param <T2>     * @return     */    public static <T1, T2> T2 copyData(T1 sourceObj, Class<T2> clazz) {        //1. 获取源数据的类        Class<?> clazz1 = sourceObj.getClass();        //2. 创建一个目标数据对象        T2 targetObj = getInstance(clazz);        //3. 复制两个对象相同的字段        copyData(sourceObj, targetObj);        return targetObj;    }    /**     * 根据字段列表复制字段的值     *     * @param sourceObj     * @param clazz     * @param fields     * @param <T1>     * @param <T2>     * @return     */    public static <T1, T2> T2 copyData(T1 sourceObj, Class<T2> clazz, String[] fields) {        //1. 获取源数据的类        Class<?> clazz1 = sourceObj.getClass();        //2. 创建一个目标数据对象        T2 targetObj = getInstance(clazz);        //3. 获取两个类字段集合        Field[] fields1 = clazz1.getDeclaredFields();        Field[] fields2 = clazz.getDeclaredFields();        //4. 复制字段        copyFieldValue(sourceObj, targetObj, fields, fields1, fields2);        return targetObj;    }    /**     * 复制两个对象中相同字段的值     *     * @param sourceObj     * @param targetObj     * @param <T1>     * @param <T2>     */    public static <T1, T2> void copyData(T1 sourceObj, T2 targetObj) {        //1. 获取两个对象的类        Class<?> clazz1 = sourceObj.getClass();        Class<?> clazz2 = targetObj.getClass();        //3. 获取两个类字段集合        Field[] fields1 = clazz1.getDeclaredFields();        Field[] fields2 = clazz2.getDeclaredFields();        //4. 遍历fields1        for (Field f1 : fields1) {            //4-1. 遍历fields2            for (Field f2 : fields2) {                //4-2. 复制字段                copyFieldValue(sourceObj, targetObj, f1, f2);            }        }    }    /**     * 根据字段表复制两个对象中相同字段的值     *     * @param sourceObj     * @param targetObj     * @param fields     * @param <T1>     * @param <T2>     */    public static <T1, T2> void copyData(T1 sourceObj, T2 targetObj, String[] fields) {        //1. 获取源两个对象的类        Class<?> clazz1 = sourceObj.getClass();        Class<?> clazz2 = targetObj.getClass();        //3. 获取两个类字段集合        Field[] fields1 = clazz1.getDeclaredFields();        Field[] fields2 = clazz2.getDeclaredFields();        //4. 复制字段        copyFieldValue(sourceObj, targetObj, fields, fields1, fields2);    }    /**     * 根据字段列表排除复制对象中相同字段的值     * 凡是在字段列表中出现的不进行复制     *     * @param sourceObj     * @param clazz     * @param fields     * @param <T1>     * @param <T2>     * @return     */    public static <T1, T2> T2 copyDataExclude(T1 sourceObj, Class<T2> clazz, String[] fields) {        //1. 获取源数据的类        Class<?> clazz1 = sourceObj.getClass();        //2. 创建一个目标数据对象        T2 targetObj = getInstance(clazz);        //3. 获取两个类字段集合        Field[] fields1 = clazz1.getDeclaredFields();        Field[] fields2 = clazz.getDeclaredFields();        //4. 复制字段        copyFieldValueExclude(sourceObj, targetObj, fields, fields1, fields2);        return targetObj;    }    /**     * 根据字段列表排除复制两个对象中相同字段的值     * 凡是在字段列表中出现的不进行复制     *     * @param sourceObj     * @param targetObj     * @param fields     * @param <T1>     * @param <T2>     */    public static <T1, T2> void copyDataExclude(T1 sourceObj, T2 targetObj, String[] fields) {        //1. 获取源两个对象的类        Class<?> clazz1 = sourceObj.getClass();        Class<?> clazz2 = targetObj.getClass();        //3. 获取两个类字段集合        Field[] fields1 = clazz1.getDeclaredFields();        Field[] fields2 = clazz2.getDeclaredFields();        //4. 复制字段        copyFieldValueExclude(sourceObj, targetObj, fields, fields1, fields2);    }    /**     * 排除字段复制     *     * @param sourceObj     * @param targetObj     * @param fields     * @param fields1     * @param fields2     * @param <T1>     * @param <T2>     */    private static <T1, T2> void copyFieldValueExclude(T1 sourceObj, T2 targetObj, String[] fields, Field[] fields1, Field[] fields2) {        for (String fieldName : fields) {            //4-1. 遍历fields1            for (Field f1 : fields1) {                //4-3. 是否匹配这个字段                if (fieldName.equals(f1.getName())) {                    //只要包含该字段就跳过                    continue;                }                //4-4. 遍历fields2                for (Field f2 : fields2) {                    //4-5. 复制字段                    copyFieldValue(sourceObj, targetObj, f1, f2);                }            }        }    }    /**     * 根据字段列表复制两个对象中相同字段的值     *     * @param sourceObj     * @param targetObj     * @param fields     * @param fields1     * @param fields2     * @param <T1>     * @param <T2>     */    private static <T1, T2> void copyFieldValue(T1 sourceObj, T2 targetObj, String[] fields, Field[] fields1, Field[] fields2) {        for (String fieldName : fields) {            //4-1. 遍历fields1            for (Field f1 : fields1) {                //4-3. 是否匹配这个字段                if (fieldName.equals(f1.getName())) {                    //4-4. 遍历fields2                    for (Field f2 : fields2) {                        //4-3. 是否匹配这个字段                        if (fieldName.equals(f2.getName())) {                            //4-2. 复制字段                            copyFieldValue(sourceObj, targetObj, f1, f2);                        }                    }                }            }        }    }    /**     * 复制字段的值     *     * @param sourceObj     * @param targetObj     * @param field1     * @param field2     * @param <T1>     * @param <T2>     */    public static <T1, T2> void copyFieldValue(T1 sourceObj, T2 targetObj, Field field1, Field field2) {        try {            //1. 判断两个字段是否名称相同而且类型相同            if (field1.getName().equals(field2.getName())                    && equalFieldsType(field1, field2)) {                //2. 获取源数据字段的值                field1.setAccessible(true);                Object value = field1.get(sourceObj);                //3. 给目标数据字段赋值                field2.setAccessible(true);                field2.set(targetObj, value);                //4. 访问权限还原                field2.setAccessible(false);                field1.setAccessible(false);            }        } catch (IllegalAccessException e) {            e.printStackTrace();        }    }    /**     * 获取一个泛型的实例     *     * @param clazz     * @param <T>     * @return     */    public static <T> T getInstance(Class<T> clazz) {        try {            return clazz.newInstance();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }        return null;    }    /**     * 判断两个字段的类型是否相同     *     * @param field1 复制源     * @param field2 复制目标     * @return     */    public static boolean equalFieldsType(Field field1, Field field2) {        String fTypeName1 = field1.getType().getSimpleName();        String fTypeName2 = field2.getType().getSimpleName();        System.out.println(fTypeName1 + ":" + fTypeName2);        //1. 处理基本数据类型和包装类        Map<String, String> map = new HashMap<String, String>();        map.put(int.class.getSimpleName(), Integer.class.getSimpleName());        map.put(byte.class.getSimpleName(), Byte.class.getSimpleName());        map.put(short.class.getSimpleName(), Short.class.getSimpleName());        map.put(char.class.getSimpleName(), Character.class.getSimpleName());        map.put(long.class.getSimpleName(), Long.class.getSimpleName());        map.put(float.class.getSimpleName(), Float.class.getSimpleName());        map.put(double.class.getSimpleName(), Double.class.getSimpleName());        map.put(boolean.class.getSimpleName(), Boolean.class.getSimpleName());        /**         * 在涉及包装类的判断逻辑中,源数据不能是包装类         * 因为包装类一旦为null,会引发异常         */        Set<String> keySet = map.keySet();        for (String key : keySet) {            if (key.equals(fTypeName1) && map.get(key).equals(fTypeName2)) {                return true;            }        }        //2. 名称相同、类型相同        if (fTypeName1.equals(fTypeName2)) {            return true;        }        return false;    }}

看看这个工具类的作用效果吧!!

我们做alipay的异步通知接口,要把异步通知结果发送到我们的消息中心,然后推送到手机端。alipay服务器发送的参数大约有28个,我们在支付处理服务建立了一个带有gson字段映射的类,很好地解析了接收的字段信息。转身,我们要将这个对象作为参数发送到消息中心。那么问题来了。我们用retrofit2调用消息中心的接口,gs使用gson将数据打包,类的字段又会根据字段映射解析为原来的样子。而消息中心接收数据是不去理会字段映射的。我们把建好的类复制到消息中心去接收,就会出现很多字段收不到的情况。我们总不能再去建一个类吧?或者改变原来的类?总之很麻烦,令人很不爽。没办法我们就只能一个字段一个字段去转移了。

不过,一旦用上这个工具类,原来的二三十行代码就可以直接缩减为一行了。我们把原来的类复制一个,去掉所有的字段映射。

看看效果:

//构建        AlipayNoticeVO alipayNoticeVO = EntityUtils.copyData(alipayNotice,AlipayNoticeVO.class);//        alipayNoticeVO.setAppId(alipayNotice.getAppId());//        alipayNoticeVO.setBody(alipayNotice.getBody());//        alipayNoticeVO.setBuyerId(alipayNotice.getBuyerId());//        alipayNoticeVO.setBuyerLogonId(alipayNotice.getBuyerLogonId());//        alipayNoticeVO.setBuyerPayAmount(alipayNotice.getBuyerPayAmount());//        alipayNoticeVO.setGmtClose(alipayNotice.getGmtClose());//        alipayNoticeVO.setGmtCreate(alipayNotice.getGmtCreate());//        alipayNoticeVO.setGmtPayment(alipayNotice.getGmtPayment());//        alipayNoticeVO.setGmtRefund(alipayNotice.getGmtRefund());//        alipayNoticeVO.setInvoiceAmount(alipayNotice.getInvoiceAmount());//        alipayNoticeVO.setNotifyId(alipayNotice.getNotifyId());//        alipayNoticeVO.setNotifyTime(alipayNotice.getNotifyTime());//        alipayNoticeVO.setNotifyType(alipayNotice.getNotifyType());//        alipayNoticeVO.setOutBizNo(alipayNotice.getOutBizNo());//        alipayNoticeVO.setOutTradeNo(alipayNotice.getOutTradeNo());//        alipayNoticeVO.setPointAmount(alipayNotice.getPointAmount());//        alipayNoticeVO.setReceiptAmount(alipayNotice.getReceiptAmount());//        alipayNoticeVO.setSellerEmail(alipayNotice.getSellerEmail());//        alipayNoticeVO.setSendBackFee(alipayNotice.getSendBackFee());//        alipayNoticeVO.setSellerId(alipayNotice.getSellerId());//        alipayNoticeVO.setSign(alipayNotice.getSign());//        alipayNoticeVO.setSignType(alipayNotice.getSignType());//        alipayNoticeVO.setSubject(alipayNotice.getSubject());//        alipayNoticeVO.setTotalAmount(alipayNotice.getTotalAmount());//        alipayNoticeVO.setTradeStatus(alipayNotice.getTradeStatus());//        alipayNoticeVO.setTradeNo(alipayNotice.getTradeNo());//        alipayNoticeVO.setFundBillList(alipayNotice.getFundBillList());        return alipayNoticeVO;

注释掉的是原来的代码。被第一行取代。

阅读全文
0 0
原创粉丝点击