java获取两个对象不同属性

最近在公司接到一个系统操作日志的开发,完成如下的一个小需求,记录相关人员在系统中的操作记录

目录

1. 分析

2. 查阅资料

3. 调用牛逼博主的API,说干就干

4. 升级版

                  2022-03-31 嵌套对象更新



1. 分析

首先要完成如上的需求,我们要有一个大致的思路,新增、删除、修改三种操作日志,其中新增前端会上传对象信息,直接生成一条日志的记录,删除操作一般会上传id,根据id查询数据库,拿到你想要的字段。

难点在与修改操作:修改操作类似新增,不过会有该条数据的标识如表id,我们可以根据id去数据库中查询旧值(需要注意的是该操作必须在事务更新前执行,不然查询出来的数据会是新值,坑点),拿到旧值对象,接下来我们只需要对比新旧值之间有哪些字段值不同即可判断,修改了哪些内容。


2. 查阅资料

接到开发任务的我毫不慌张,毕竟网上的资源一大堆,根据自己的需求去问度娘................巴拉巴拉,终于找到一篇文章可以为我所用 Java比较两个对象并获取其中不相等的字段

该篇文章主要讲如何获取两个对象中不相等的字段(前提是类对象相同哈)。。。大家可直接前往参谋.....我这里不再赘述。

API调用工程师终于等到你。。不想看的人直接复制下面的依赖吧。。。


<dependency>
     <groupId>com.github.dadiyang</groupId>
     <artifactId>equator</artifactId>
     <version>1.0.3</version>
</dependency>

3. 调用牛逼博主的API,说干就干

核心接口代码如下:

接口实现继承如下,每个方法该文的博主介绍的很详细,我这里简单说几句:

FieldBaseEquator 抽象类实现,我们主要用到这里类。


一个优秀的程序员,必须要学会 copy 大神的优秀代码,来吧上代码,先编写一个实体User类

public class User {
    
    
    private int age;
    
    private String name;
    
    private List<String> friendName;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getFriendName() {
        return friendName;
    }

    public void setFriendName(List<String> friendName) {
        this.friendName = friendName;
    }
}
   

执行main方法,没有什么问题~

public static void main(String[] args) {

        User user = new User();
        user.setAge(25);
        user.setFriendName(Arrays.asList("chq","tom","Thompson"));
        user.setName("ChenHQ");


        User user1 = new User();
        user1.setAge(28);
        user1.setFriendName(Arrays.asList("chq2","tom"));
        user1.setName("ChenHQ");


        FieldBaseEquator fieldBaseEquator = new FieldBaseEquator();

        List<FieldInfo> diffFields = fieldBaseEquator.getDiffFields(user, user1);

        for (FieldInfo diffField : diffFields) {

            System.out.println(diffField);

        }

}

 运行结果如下:

我们可以看到,通过 getDiffFields 方法,我们可以获取到实体类“值”不同的属性

4. 升级版

我们通过博主的构造方法得到,可以指定某些字段进行对比,或者指定某些字段不进行对比,只需要在构造方法中传入属性名称即可

    /**
     * 指定包含或排除某些字段
     *
     * @param includeFields 包含字段,若为 null 或空集,则不指定
     * @param excludeFields 排除字段,若为 null 或空集,则不指定
     */
    public FieldBaseEquator(List<String> includeFields, List<String> excludeFields) {
        super(includeFields, excludeFields);
    }

来实现一下吧,首先编写一个自定义注解,该注解标识在属性字段上,用来注明是否需要对比的字段

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationDemo {

    String value() default "";

}

 我们在 年龄,名字 字段上加上声明的注解  @AnnotationDemo,其他不做修改

public class User {


    @AnnotationDemo("年龄")
    private int age;

    @AnnotationDemo("姓名")
    private String name;

    private List<String> friendName;

    ......
}

写一个静态方法,返回对象中被注解注释的属性名集合 (即: age,name)

    /**
     * 获取对象注解注释过的属性名集合
     *
     * @param obj
     * @return
     */
    private static List<String> getImportFieldName(Object obj) {
        //创建集合对象,保存是否是重要字段名
        List<String> importFieldName = new ArrayList<>();

        //获取类加载器对象
        Class studentClass = obj.getClass();

        //获取所有的字段
        Field[] declaredFields = studentClass.getDeclaredFields();

        for (Field declaredField : declaredFields) {
            if (declaredField.isAnnotationPresent(AnnotationDemo.class)) {
                //保存
                importFieldName.add(declaredField.getName());
            }
        }
        return importFieldName;
    }

执行如下main方法

public static void main(String[] args) {

        User user = new User();
        user.setAge(25);
        user.setFriendName(Arrays.asList("chq","tom","Thompson"));
        user.setName("ChenHQ");


        User user1 = new User();
        user1.setAge(28);
        user1.setFriendName(Arrays.asList("chq2","tom"));
        user1.setName("ChenHQ");

        //调用静态方法,获取需要比较的字段名集合
        List<String> importFieldName = getImportFieldName(user);

        //调用带参构造方法
        FieldBaseEquator fieldBaseEquator = new FieldBaseEquator(importFieldName,null);

        List<FieldInfo> diffFields = fieldBaseEquator.getDiffFields(user, user1);

        for (FieldInfo diffField : diffFields) {

            System.out.println(diffField);

        }
}

此时运行结果如下:程序只检测 age,name字段,其他不做比较

这时,我们的底层对比已经差不多实现。只是对比过后返回的信息我们处理下,我们期望返回如下实体类集合形式,方便我们操作,来吧,我们进一步封装一下代码

public class ModifiedField {

     /**
     * 被修改的字段名
     */
    private String fieldName;

    /**
     * 被修改的旧值
     */
    private Object oldValue;
    /**
     * 被修改的新值
     */
    private Object newValue;

    /**
     * 被修改的字段名中文解释
     */
    private String remark;
    ......   

}
    /**
     * 比较并返回被修改过的对象属性 ModifiedField
     *
     * @param modifyObject: 新值对象
     * @param oldObject:    旧值对象
     * @return
     */
    public static List<ModifiedField> compareObject(Object modifyObject, Object oldObject) {
        //创建结果对象
        List<ModifiedField> result = new ArrayList<>();

        //获取对比字段
        List<String> importFieldName = getImportFieldName(modifyObject);

        FieldBaseEquator fieldBaseEquator = new FieldBaseEquator(importFieldName, null);
        
        //对比,返回属性值不同字段
        List<FieldInfo> diffFields = fieldBaseEquator.getDiffFields(modifyObject, oldObject);
        
        //封装成我们需要的对象
        for (FieldInfo diffField : diffFields) {

            ModifiedField modifiedField = new ModifiedField();
            modifiedField.setFieldName(diffField.getFieldName());
            modifiedField.setNewValue(diffField.getFirstVal());
            modifiedField.setOldValue(diffField.getSecondVal());

            //获取字段注解中文释义
            String remark = getRemarkByFieldName(modifyObject, diffField.getFieldName());
            modifiedField.setRemark(remark);

            result.add(modifiedField);

        }
        return result;
    }



    /**
     * 根据传入的对象,和字段名,获取对应注解的中文名称
     *
     * @param obj
     * @param fieldName
     * @return
     */
    private static String getRemarkByFieldName(Object obj, String fieldName) {
        Class<?> aClass = obj.getClass();

        try {
            Field field = aClass.getDeclaredField(fieldName);

            if (field.isAnnotationPresent(AnnotationDemo.class)) {

                AnnotationDemo annotation = field.getAnnotation(AnnotationDemo.class);
                return annotation.value();

            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

执行main方法

 public static void main(String[] args) {

        User user = new User();
        user.setAge(25);
        user.setFriendName(Arrays.asList("chq","tom","Thompson"));
        user.setName("ChenHQ");


        User user1 = new User();
        user1.setAge(28);
        user1.setFriendName(Arrays.asList("chq2","tom"));
        user1.setName("ChenHQ");


        List<ModifiedField> modifiedFields = compareObject(user, user1);

        for (ModifiedField modifiedField : modifiedFields) {
            System.out.println(modifiedField);
        }
}

如期而遇~,接下来的文案入库操作,我就不多说了。。愿你们码农之路,一路顺畅


2022-03-31 嵌套对象更新

很多小伙伴私信我能不能支持嵌套对象查询,这里更新一版

我们这里需要重写一个字段比较器 MyFieldBaseEquator,使用了递归查询属性,代码如下


/**
 * @author ChenHQ
 * @description: TODO
 * @date 2022/3/31 18:43
 */
public class MyFieldBaseEquator extends AbstractEquator {
    public MyFieldBaseEquator() {
    }

    /**
     * 指定包含或排除某些字段
     *
     * @param includeFields 包含字段,若为 null 或空集,则不指定
     * @param excludeFields 排除字段,若为 null 或空集,则不指定
     */
    public MyFieldBaseEquator(List<String> includeFields, List<String> excludeFields) {
        super(includeFields, excludeFields);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<FieldInfo> getDiffFields(Object first, Object second) {
        if (first == second) {
            return Collections.emptyList();
        }
        // 先尝试判断是否为简单数据类型
        if (isSimpleField(first, second)) {
            return compareSimpleField(first, second);
        }
        Object obj = first == null ? second : first;
        Class<?> clazz = obj.getClass();
        List<FieldInfo> diffField = new LinkedList<>();
        // 获取所有字段
        Field[] fields = clazz.getDeclaredFields();
        // 遍历所有的字段
        for (Field firstField : fields) {
            String fieldName = firstField.getName();

            try {
                Field secondField = clazzSecond.getDeclaredField(fieldName);
                firstField.setAccessible(true);
                secondField.setAccessible(true);
                // 开启访问权限,否则获取私有字段会报错
                Object firstVal = first == null ? null : firstField.get(first);
                Object secondVal = second == null ? null : secondField.get(second);
                if (this.WRAPPER.contains(firstField.getType()) || (firstVal instanceof Collection && secondVal instanceof Collection)) {
                    // 封装字段信息
                    FieldInfo fieldInfo = new FieldInfo(fieldName, firstField.getType(), firstVal, secondVal);
                    boolean eq = isFieldEquals(fieldInfo);
                    if (!eq) {
                        // 记录不相等的字段
                        diffField.add(fieldInfo);
                    }
                } else if (onlyOneNull(firstVal, secondVal)) {
                    FieldInfo fieldInfo = new FieldInfo(fieldName, firstField.getType(), firstVal, secondVal);
                    diffField.add(fieldInfo);
                } else if (Objects.isNull(firstVal) && Objects.isNull(secondVal)) {
                    //双方都为null,不做记录
                    continue;
                } else {
                    diffField.addAll(getDiffFields(firstVal, secondVal));
                }
            } catch (IllegalAccessException e) {
                // 只要调用了 firstField.setAccessible(true) 就不会报这个异常
                throw new IllegalStateException("获取属性进行比对发生异常: " + fieldName, e);
            } catch (NoSuchFieldException e) {
            }
        }
        return diffField;
    }

    private boolean onlyOneNull(Object firstVal, Object secondVal) {
        return (Objects.isNull(firstVal) && Objects.nonNull(secondVal)) || (Objects.isNull(secondVal) && Objects.nonNull(firstVal));
    }


    /**
     * 如果简单数据类型的对象则直接进行比对
     *
     * @param first  对象1
     * @param second 对象2
     * @return 不同的字段信息,相等返回空集,不等则 FieldInfo 的字段名为对象的类型名称
     */
    List<FieldInfo> compareSimpleField(Object first, Object second) {
        boolean eq = Objects.equals(first, second);
        if (eq) {
            return Collections.emptyList();
        } else {
            Object obj = first == null ? second : first;
            Class<?> clazz = obj.getClass();
            // 不等的字段名称使用类的名称
            return Collections.singletonList(new FieldInfo(clazz.getSimpleName(), clazz, first, second));
        }
    }

    private static final List<Class<?>> WRAPPER = Arrays.asList(Byte.class, Short.class,
            Integer.class, Long.class, Float.class, Double.class, Character.class,
            Boolean.class, String.class);

    /**
     * 判断是否为原始数据类型
     *
     * @param first  对象1
     * @param second 对象2
     * @return 是否为原始数据类型
     */
    boolean isSimpleField(Object first, Object second) {
        Object obj = first == null ? second : first;
        Class<?> clazz = obj.getClass();
        return clazz.isPrimitive() || WRAPPER.contains(clazz);
    }
}

 结果通过测试,这里不做展示了,仅需注意使用我们自定义重写的类MyFieldBaseEquator即可

public static void main(String[] args) {

        ..............

        //自定义重写的类
        MyFieldBaseEquator fieldBaseEquator = new MyFieldBaseEquator(importFieldName,null);

        List<FieldInfo> diffFields = fieldBaseEquator.getDiffFields(user, user1);

        for (FieldInfo diffField : diffFields) {

            System.out.println(diffField);

        }
}

  • 10
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值