比较几种javabean属性的最佳方法是什么?

2022-09-03 05:25:59

我需要比较两个对象(同一类的实例)中的数十个字段,并进行一些日志记录和更新,以防存在差异。元代码可能如下所示:

if (a.getfield1 != b.getfield1)
  log(a.getfield1 is different than b.getfield1)
  b.field1 = a.field1

if (a.getfield2!= b.getfield2)
  log(a.getfield2 is different than b.getfield2)
  b.field2 = a.field2

...

if (a.getfieldn!= b.getfieldn)
  log(a.getfieldn is different than b.getfieldn)
  b.fieldn = a.fieldn

包含所有比较的代码非常简洁,我想以某种方式使其更加紧凑。如果我能有一个方法,它将作为参数方法调用 setter 和 getter,并调用它为所有字段,那就太好了,但不幸的是,这在 java 中是不可能的。

我提出了三种选择,每种方案都有自己的缺点。

1. 使用反射 API 找出 Getter 和 setters
Ugly,如果字段名称更改,可能会导致运行时错误

2. 将字段更改为公共字段并直接操作它们,而无需使用 getter 和 setters
丑陋,并且会将类的实现暴露给外部世界

3. 让包含类(实体)进行比较,更新更改的字段并返回日志消息
实体不应参与业务逻辑

所有字段都是 String 类型,如果需要,我可以修改拥有这些字段的类的代码。

编辑:类中有一些字段不能进行比较。


答案 1

使用批注

如果标记需要比较的字段(无论它们是否为私有字段),您仍然不会丢失封装,然后获取这些字段并进行比较。它可能如下所示:

在需要比较的类中:

@ComparableField 
private String field1;

@ComparableField
private String field2;

private String field_nocomparable;

在外部类中:

public <T> void compare(T t, T t2) throws IllegalArgumentException,
                                          IllegalAccessException {
    Field[] fields = t.getClass().getDeclaredFields();
    if (fields != null) {
        for (Field field : fields) {
            if (field.isAnnotationPresent(ComparableField.class)) {
                field.setAccessible(true);
                if ( (field.get(t)).equals(field.get(t2)) )
                    System.out.println("equals");
                field.setAccessible(false);
            }
        }
    }
}

代码未经过测试,但如果有帮助,请告诉我。


答案 2

JavaBeans API旨在帮助进行自省。自Java版本1.2以来,它一直以这种或那种形式存在,并且自1.4版本以来一直非常有用。

比较两个 Bean 中的属性列表的演示代码:

  public static void compareBeans(PrintStream log,
      Object bean1, Object bean2, String... propertyNames)
      throws IntrospectionException,
      IllegalAccessException, InvocationTargetException {
    Set<String> names = new HashSet<String>(Arrays
        .asList(propertyNames));
    BeanInfo beanInfo = Introspector.getBeanInfo(bean1
        .getClass());
    for (PropertyDescriptor prop : beanInfo
        .getPropertyDescriptors()) {
      if (names.remove(prop.getName())) {
        Method getter = prop.getReadMethod();
        Object value1 = getter.invoke(bean1);
        Object value2 = getter.invoke(bean2);
        if (value1 == value2
            || (value1 != null && value1.equals(value2))) {
          continue;
        }
        log.format("%s: %s is different than %s%n", prop
            .getName(), "" + value1, "" + value2);
        Method setter = prop.getWriteMethod();
        setter.invoke(bean2, value2);
      }
    }
    if (names.size() > 0) {
      throw new IllegalArgumentException("" + names);
    }
  }

示例调用:

compareBeans(System.out, bean1, bean2, "foo", "bar");

如果采用注释路径,请考虑转储反射并使用编译时注释处理器或其他代码生成器生成比较代码。