java对象对比工具 自定义注解对象属性对比 支持递归调用 无三方包纯原生工具 简单实用 反射来对比对象的差异 增强字典说明

场景

在很多情况下,尤其是单表的版本控制的时候,需要记录后台人员的操作日志,通常是用到两个java对象去坐对比,告诉后台人员,改了哪些字段,就好比[名字:李四->张三],名字由原来的李四变成了张三。那么不废话了,直接亮出我反射结合注解写的对象对比工具。

字段对比 自定义注解

import java.lang.annotation.*;
/**
 * 字段比较注解
 * @author liaoqian
 * @since 2023/8/11
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface FiledCompara {
    String value() ;
    FiledComparaTypeEnum type() default FiledComparaTypeEnum.DEFAULT;
}

字典对比枚举类

import lombok.Getter;

@Getter
public enum FiledComparaTypeEnum {
    DEFAULT("default", null),
    SEX("sex", new SexFiledComparaType());

    private String type;
    private IFiledComparaType filedComparaType;

    FiledComparaTypeEnum(String type, IFiledComparaType filedComparaType) {
        this.type = type;
        this.filedComparaType = filedComparaType;
    }

    public boolean isDefault() {
        return DEFAULT.type.equals(type);
    }
}

测试对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
   
    @FiledCompara("名字")
    private String name;
    
    @FiledCompara("密码")
    private String password;
    
    @FiledCompara("儿子")
    private User son;
	
	@FiledCompara(value = "性别",type = FiledComparaTypeEnum.SEX)
    private Integer sex;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

	public User(String name, Integer sex) {
        this.name = name;
        this.sex= sex;
    }
}

接口

import java.util.Map;

public interface IFiledComparaType {
    Map<String,String> map();
}

性别实现类

import java.util.HashMap;
import java.util.Map;

/**
 * @author liaoqian
 * @since 2023/8/24
 */
public class SexFiledComparaType implements IFiledComparaType {

    @Override
    public Map<String, String> map() {
        Map<String, String> ans = new HashMap<>();
        ans.put("1","男");
        ans.put("0","女");
        return ans;
    }
}

对象对比工具类

package com.example.springboottest.util;


import com.example.springboottest.annotation.FiledCompara;
import com.example.springboottest.enums.FiledComparaTypeEnum;
import com.example.springboottest.enums.IFiledComparaType;

import java.lang.reflect.Field;
import java.util.*;

/**
 * 对象比较工具
 *
 * @author liaoqian
 * @since 2023/8/11
 */
public class ObjectComparaUtil {

    private static final Map<Class<?>, Class<?>> primitiveWrapperTypeMap = new IdentityHashMap(8);

    static {
        primitiveWrapperTypeMap.put(Boolean.class, Boolean.TYPE);
        primitiveWrapperTypeMap.put(Byte.class, Byte.TYPE);
        primitiveWrapperTypeMap.put(Character.class, Character.TYPE);
        primitiveWrapperTypeMap.put(Double.class, Double.TYPE);
        primitiveWrapperTypeMap.put(Float.class, Float.TYPE);
        primitiveWrapperTypeMap.put(Integer.class, Integer.TYPE);
        primitiveWrapperTypeMap.put(Long.class, Long.TYPE);
        primitiveWrapperTypeMap.put(Short.class, Short.TYPE);
    }

    public static Map<String, String> comparaObj(Object target, Object source) {
        return comparaObj(target, source, null);
    }

    private static Map<String, String> comparaObj(Object target, Object source, Map<String, String> map) {
        Map<String, String> ans = new LinkedHashMap<>(1 << 6);
        if (source == null || target == null) {
            throw new RuntimeException("比较的两个对象不能为空");
        }
        Class clazz = source.getClass();
        if (!clazz.equals(target.getClass())) {
            throw new RuntimeException("比较的两个对象类型不同");
        }
        Field[] fields = clazz.getDeclaredFields();
        String format = " %s:%s->%s ";
        for (Field field : fields) {
            FiledCompara filedCompara = field.getAnnotation(FiledCompara.class);
            if (filedCompara != null) {
                field.setAccessible(true);
                Object sourceFieldValue = null;
                Object targetFieldValue = null;
                try {
                    sourceFieldValue = field.get(source);
                    targetFieldValue = field.get(target);
                } catch (Exception e) {

                }
                boolean notNull = sourceFieldValue != null && targetFieldValue != null;
                boolean primitive = isPrimitiveType(field.getType());
                if (!notNull) {
                    continue;
                }
                String entityKey = field.getName();
                String value = filedCompara.value();
                FiledComparaTypeEnum type = filedCompara.type();

                value = value == null || "".equals(value.trim()) ? entityKey : value;
                if (primitive) {
                    String sourceFieldValueStr = sourceFieldValue.toString();
                    String targetFieldValueStr = targetFieldValue.toString();
                    if (!sourceFieldValueStr.equals(targetFieldValueStr)) {
                        Map<String, String> valueDictMap = getValueDictMap(type);
                        String entityValue = String.format(format, value,  valueDictMap.getOrDefault(sourceFieldValueStr,sourceFieldValueStr),valueDictMap.getOrDefault(targetFieldValueStr,targetFieldValueStr));
                        ans.put(entityKey, entityValue);
                    }
                } else {
                    Map<String, String> sonMap = comparaObj(sourceFieldValue, targetFieldValue, ans);
                    if (!sonMap.isEmpty()) {
                        for (Map.Entry<String, String> entry : sonMap.entrySet()) {
                            ans.put(entityKey + "." + entry.getKey(), value + "." + entry.getValue());
                        }
                    }
                }
            }

        }


        return ans;
    }

    private static Map<String, String> getValueDictMap(FiledComparaTypeEnum filedComparaType) {
        if (filedComparaType.isDefault()) {
            return new HashMap<>();
        }
        IFiledComparaType filedComparaTypeImpl = filedComparaType.getFiledComparaType();
        return filedComparaTypeImpl.map();
    }

    private static boolean isPrimitiveType(Class clazz) {
        boolean ans = false;
        if (clazz.isPrimitive()) {
            return true;
        }
        boolean containsKey = primitiveWrapperTypeMap.containsKey(clazz);
        if (containsKey) {
            return true;
        }
        String simpleName = clazz.getSimpleName();
        switch (simpleName) {
            case "String":
                ans = true;
                break;
            default:
                break;
        }
        return ans;
    }
}


单元对比测试一:简单对象对比

测试代码

	@Test
    public void tt() {
        User sourceUser = new User("李四", "qwertyuiop");
        User targetUser = new User("张三", "qwertyui0p");
        Map<String, String> map = ObjectComparaUtil.comparaObj(sourceUser, targetUser);
        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println(entry.getKey()+" --- "+entry.getValue());
        }
    }

测试结果

name --- 名字:李四->张三 
password --- 密码:qwertyuiop->qwertyui0p 

单元对比测试二:复杂对象对比

测试代码

	@Test
    public void tt() {
        User sourceUser = new User("张三", "qwertyuiop",new User("张三的儿子","123456",new User("张三的孙子女","666666")));
        User targetUser = new User("张三", "qwertyui0p",new User("张三的儿子","12345678",new User("张三的孙子男","666666")));
        Map<String, String> map = ObjectComparaUtil.comparaObj(sourceUser, targetUser);
        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println(entry.getKey()+" --- "+entry.getValue());
        }
    }

测试结果

password --- 密码:qwertyuiop->qwertyui0p 
son.password --- 儿子.密码:123456->12345678 
son.son.name --- 儿子.儿子.名字:张三的孙子女->张三的孙子男 

使用了字典对比效果

测试代码

@Test
    public void tt() {
        User targetUser = new User("王小丫",0);
        User sourceUser = new User("张三",1);
        Map<String, String> map = ObjectComparaUtil.comparaObj(sourceUser, targetUser);
        for (Map.Entry<String, String> entry : map.entrySet()) {
            System.out.println(entry.getKey()+" --- "+entry.getValue());
        }
    }

测试结果

name ---  名字:王小丫->张三 
sex ---  性别:->

亮点细节

  • 纯原生代码,无三方依赖
  • 支持复杂对象的对比
  • 效率高,逻辑清晰
  • 可扩展性高
  • 这里要配合你对象版本的管理,比如同一个user对象v4和v3的对比要保存在表中

可扩展性

  1. 对象字段字典上对比有缺陷的地方
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

若光672

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值