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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xxkalychen/article/details/78699053

需求的来源是项目。

每次做项目,都要建立很多的数据体,有的是数据库表产生的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;

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

猜你喜欢

转载自blog.csdn.net/xxkalychen/article/details/78699053