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

我需要比较两个对象(同一个类的实例)中的几十个字段,并在存在差异时进行一些记录和更新。 元代码看起来像这样:

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.使用reflectionAPI找出getter和setter
丑陋并且在字段名称更改的情况下可能导致运行时错误

2.将字段更改为public并直接操作它们,而不使用getter和setter
丑陋也会将类的实现暴露给外部世界

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

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

编辑:类中有一些字段不得比较。

使用注释 。

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

在需要比较的类中:

 @ComparableField private String field1; @ComparableField private String field2; private String field_nocomparable; 

而在外部类:

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

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

JavaBeans API旨在帮助进行内省。 自Java版本1.2以来,它已经以某种forms存在,并且自1.4版本以来一直非常有用。

演示代码,用于比较两个bean中的属性列表:

  public static void compareBeans(PrintStream log, Object bean1, Object bean2, String... propertyNames) throws IntrospectionException, IllegalAccessException, InvocationTargetException { Set names = new HashSet(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"); 

如果您转到注释路由,请考虑转储reflection并使用编译时注释处理器或其他代码生成器生成比较代码。

我会选择选项1,但我会使用getClass().getDeclaredFields()来访问字段而不是使用名称。

 public void compareAndUpdate(MyClass other) throws IllegalAccessException { for (Field field : getClass().getDeclaredFields()) { if (field.getType() == String.class) { Object thisValue = field.get(this); Object otherValue = field.get(other); // if necessary check for null if (!thisValue.equals(otherValue)) { log(field.getName() + ": " + thisValue + " <> " + otherValue); field.set(other, thisValue); } } } } 

这里有一些限制(如果我是对的):

  • 比较方法必须在同一个类中实现(在我看来它应该 – 无论其实现如何)不在外部类中。
  • 只使用此类中的字段,而不是来自超类的字段。
  • 必须处理IllegalAccessException(我只是在上面的例子中抛出它)。

这可能也不太好,但它比你提出的两种选择中的任何一种都要少得多(恕我直言)。

如何提供一个获取数字索引字段的getter / setter对,然后让getter / setter将索引字段取消引用到相关的成员变量?

即:

 public class MyClass { public void setMember(int index, String value) { switch (index) { ... } } public String getMember(int index) { ... } static public String getMemberName(int index) { ... } } 

然后在你的外部课堂上:

 public void compareAndUpdate(MyClass a, MyClass b) { for (int i = 0; i < a.getMemberCount(); ++i) { String sa = a.getMember(); String sb = b.getMember(); if (!sa.equals(sb)) { Log.v("compare", a.getMemberName(i)); b.setMember(i, sa); } } } 

这至少允许您将所有重要逻辑保留在正在检查的类中。

虽然选项1可能很难看,但它可以完成任务。 选项2甚至更加丑陋,并为您无法想象的漏洞打开代码。 即使您最终排除了选项1,我仍然建议您保留现有代码而不是选项2。

话虽如此,如果您不想将此作为静态列表传递给方法,则可以使用reflection来获取类的字段名称列表。 假设您要比较所有字段,则可以在循环中动态创建比较。

如果不是这种情况,并且您比较的字符串只是某些字段,则可以进一步检查字段并仅隔离String类型的String ,然后继续比较。

希望这可以帮助,

Yuval = 8-)

以来

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

你可以试试这堂课:

 public class BigEntity { private final Map data; public LongEntity() { data = new HashMap(); } public String getFIELD1() { return data.get(FIELD1); } public String getFIELD2() { return data.get(FIELD2); } /* blah blah */ public void cloneAndLogDiffs(BigEntity other) { for (String field : fields) { String a = this.get(field); String b = other.get(field); if (!a.equals(b)) { System.out.println("diff " + field); other.set(field, this.get(field)); } } } private String get(String field) { String value = data.get(field); if (value == null) { value = ""; } return value; } private void set(String field, String value) { data.put(field, value); } @Override public String toString() { return data.toString(); } 

魔法代码:

  private static final String FIELD1 = "field1"; private static final String FIELD2 = "field2"; private static final String FIELD3 = "field3"; private static final String FIELD4 = "field4"; private static final String FIELDN = "fieldN"; private static final List fields; static { fields = new LinkedList(); for (Field field : LongEntity.class.getDeclaredFields()) { if (field.getType() != String.class) { continue; } if (!Modifier.isStatic(field.getModifiers())) { continue; } fields.add(field.getName().toLowerCase()); } } 

这个类有几个优点:

  • 在class级装载时反映一次
  • 它只是添加新的字段,只需添加新的静态字段(这里更好的解决方案是使用Annotations:在你关心使用reflection的情况下也可以使用java 1.4)
  • 你可以在一个抽象类中重构这个类,所有派生类都可以得到它们
    data和cloneAndLogDiffs()
  • 外部接口是类型安全的(你也可以很容易地强加不变性)
  • 没有setAccessible调用:这种方法有时会出问题

一个广泛的想法:

创建一个新类,其对象采用以下参数:要比较的第一个类,要比较的第二个类,以及对象的getter和setter方法名称列表,其中仅包含感兴趣的方法。

您可以使用reflection查询对象的类,并从中查询其可用的方法。 假设参数列表中的每个getter方法都包含在类的可用方法中,您应该能够调用该方法来获取用于比较的值。

粗略勾勒出类似的东西(道歉,如果它不是超级完美……不是我的主要语言):

 public class MyComparator { //NOTE: Class a is the one that will get the value if different //NOTE: getters and setters arrays must correspond exactly in this example public static void CompareMyStuff(Object a, Object b, String[] getters, String[] setters) { Class a_class = a.getClass(); Class b_class = b.getClass(); //the GetNamesFrom... static methods are defined elsewhere in this class String[] a_method_names = GetNamesFromMethods(a_class.getMethods()); String[] b_method_names = GetNamesFromMethods(b_class.getMethods()); String[] a_field_names = GetNamesFromFields(a_class.getFields()); //for relative brevity... Class[] empty_class_arr = new Class[] {}; Object[] empty_obj_arr = new Object[] {}; for (int i = 0; i < getters.length; i++) { String getter_name = getter[i]; String setter_name = setter[i]; //NOTE: the ArrayContainsString static method defined elsewhere... //ensure all matches up well... if (ArrayContainsString(a_method_names, getter_name) && ArrayContainsString(b_method_names, getter_name) && ArrayContainsString(a_field_names, setter_name) { //get the values from the getter methods String val_a = a_class.getMethod(getter_name, empty_class_arr).invoke(a, empty_obj_arr); String val_b = b_class.getMethod(getter_name, empty_class_arr).invoke(b, empty_obj_arr); if (val_a != val_b) { //LOG HERE //set the value a_class.getField(setter_name).set(a, val_b); } } else { //do something here - bad names for getters and/or setters } } } } 

你说你现在有所有这些领域的吸气剂和制定者吗? 好的,然后将基础数据从一堆单个字段更改为数组。 更改所有getter和setter以访问该数组。 我会为索引创建常量标记,而不是使用数字来实现长期可维护性。 还要创建一个并行的标志数组,指示应该处理哪些字段。 然后创建一个使用索引的通用getter / setter对,以及compare标志的getter。 像这样的东西:

 public class SomeClass { final static int NUM_VALUES=3; final static int FOO=0, BAR=1, PLUGH=2; String[] values=new String[NUM_VALUES]; static boolean[] wantCompared={true, false, true}; public String getFoo() { return values[FOO]; } public void setFoo(String foo) { values[FOO]=foo; } ... etc ... public int getValueCount() { return NUM_VALUES; } public String getValue(int x) { return values[x]; } public void setValue(int x, String value) { values[x]=value; } public boolean getWantCompared(int x) { return wantCompared[x]; } } public class CompareClass { public void compare(SomeClass sc1, SomeClass sc2) { int z=sc1.getValueCount(); for (int x=0;x 

我只是把它写在了我的头顶,我还没有测试过,所以它们可能是代码中的错误,但我认为这个概念应该有效。

由于您已经拥有getter和setter,因此使用此类的任何其他代码应继续保持不变。 如果没有其他代码使用此类,则抛弃现有的getter和setter,只需对数组执行所有操作。

我也会提出一个类似于Alnitak的解决方案。

如果在比较时需要迭代字段,为什么不省略单独的字段,并将数据放入数组,HashMap或类似的适当的东西。

然后,您可以通过编程方式访问它们,比较它们等。如果需要以不同的方式处理和比较不同的字段,您可以为值创建适当的帮助程序类,从而实现接口。

然后你可以这样做

 valueMap.get("myobject").compareAndChange(valueMap.get("myotherobject") 

或类似的规定…

我正在编写一个名为jComparison( https://github.com/mmirwaldt/jcomparison )的框架,它找到两个java对象,字符串,映射和集合之间的差异(和相似之处)。 有许多演示向您展示如何使用它。 然而,这是一个alpha版本atm,我还没有决定我想选择哪个许可证。

注意:我是该框架的作者。

请参阅https://github.com/mmirwaldt/jcomparison/blob/master/core-demos/src/main/java/net/mirwaldt/jcomparison/core/object/ComparePersonsDemo.java下的演示。

它显示了一个虚构类Person和Address的示例。 演示的输出是:

 Similarities: private final net.mirwaldt.jcomparison.core.object.Person$Sex net.mirwaldt.jcomparison.core.object.Person.sex: MALE Differences: private final double net.mirwaldt.jcomparison.core.object.Person.moneyInPocket: ImmutableDoublePair{leftDouble=35.12, rightDouble=148.96} private final int net.mirwaldt.jcomparison.core.object.Person.age: ImmutableIntPair{leftInt=32, rightInt=45} Comparisons: name: personA : 'Mich[a]el' personB : 'Mich[]el' Features: Feature of personA only : '{WRISTWATCH_BRAND=CASIO}' Feature of personB only : '{TATTOO_TEXT=Mum}' personA and personB have different features : '{SKIN_COLOR=ImmutablePair [leftValue= white, rightValue= black]}' personA and personB have similar features: '{HAIR_COLOR=brown}' Leisure activities: Leisure activities of personA only : '[Piano]' Leisure activities of personB only : '[Tennis, Jogging]' personA and personB have similar leisureActivities: '[Swimming]' Address: Similarities: private final int net.mirwaldt.jcomparison.core.object.Address.zipCode: 81245 private final net.mirwaldt.jcomparison.core.object.Address$Country net.mirwaldt.jcomparison.core.object.Address.country: GERMANY private final java.lang.String net.mirwaldt.jcomparison.core.object.Address.streetName: August-Exter-Str. private final java.lang.String net.mirwaldt.jcomparison.core.object.Address.city: Munich Differences: private final int net.mirwaldt.jcomparison.core.object.Address.houseNumber: ImmutableIntPair{leftInt=10, rightInt=12}