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() ;
}

测试对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
   
    @FiledCompara("名字")
    private String name;
    
    @FiledCompara("密码")
    private String password;
    
    @FiledCompara("儿子")
    private User son;

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

对象对比工具类

import com.example.springboottest.annotation.FiledCompara;

import java.lang.reflect.Field;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 对象比较工具
 *
 * @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);
    }

    /**
     * 对象对比
     * @param source 原始对象
     * @param target 目标对象
     * @return
     */
    public static Map<String, String> comparaObj(Object source, Object target) {
        return comparaObj(source, target, null);
    }

    /**
     * 
     * @param source
     * @param target
     * @param map
     * @return
     */
    private static Map<String, String> comparaObj(Object source, Object target, 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();
                value = value == null || "".equals(value.trim()) ? entityKey : value;
                if (primitive) {
                    String sourceFieldValueStr = sourceFieldValue.toString();
                    String targetFieldValueStr = targetFieldValue.toString();
                    if (!sourceFieldValueStr.equals(targetFieldValueStr)) {
                        String entityValue = String.format(format, value, sourceFieldValueStr, 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;
    }

    /**
     * 是不是基本类型、包装类、String
     * @param clazz
     * @return
     */
    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 --- 儿子.儿子.名字:张三的孙子女->张三的孙子男 

亮点细节

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

可扩展性

  1. 可以在注解里定义枚举,翻译对象属性的枚举,比如 [状态:未审核->审核通过]

你可能感兴趣的:(基础,java,迭代加深,intellij,idea)