通用的反射辅助方法,用于equals和hashCode

3
我想创建一个反射辅助方法用于equals和hashCode。对于equals,该辅助方法使用反射API查看objectA的字段并将其与objectB的字段进行比较。对于hashCode,该辅助方法使用反射API查看字段并在迭代循环中计算hashCode。
好处是我不需要担心在equals或hashCode实现中缺少字段。坏处是可能会影响性能。您认为这个想法如何?请分享您的意见!
这是我的equals初稿:
public final class ReflectiveEqualsHelper {

public static boolean isEqual(final Object a, final Object b) {
    if (!isTypeEqual(a, b)) {
        return false;
    }

    Field[] fields = getFields(a);

    Object valueA;
    Object valueB;
    String fieldName;
    for (int i = 0; i < fields.length; i++) {
        fieldName = fields[i].getName();
        valueA = getValueByFieldName(a, fieldName);
        valueB = getValueByFieldName(b, fieldName);
        if (!compare(valueA, valueB)) {
            return false;
        }
    }
    return true;
}

@SuppressWarnings("rawtypes")
private static Field[] getFields(final Object o) {
    Class clazz = o.getClass();
    Field[] fields = clazz.getDeclaredFields();
    return fields;
}

private static Field getField(final Object o, final String name) {
    try {
        Field field = o.getClass().getDeclaredField(name);
        return field;
    } catch (NoSuchFieldException e) {
        throw new RuntimeException(e);
    }
}

private static Object getValueByFieldName(final Object o, final String name) {
    Field field = getField(o, name);
    field.setAccessible(true);

    try {
        Object value = field.get(o);
        field.setAccessible(false);
        return value;
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }

}

private static boolean areBothNull(final Object a, final Object b) {
    return (a == null && b == null);
}

private static boolean isTypeEqual(final Object a, final Object b) {
    if (areBothNull(a, b)) {
        return false;
    }

    return a.getClass().equals(b.getClass());
}

private static boolean compare(final Object a, final Object b) {
    if (a == null) {
        return false;
    } else if (b == null) {
        return false;
    }
    return a.equals(b);
}

}

public class ReflectiveEqualsHelperTest {

@Test
public void testIsEqual() {
    Vector a = new Vector(Long.valueOf(1L), 3L);
    Vector b = new Vector(Long.valueOf(1L), 3L);
    Vector c = new Vector(Long.valueOf(2L), 3L);
    boolean testA = ReflectiveEqualsHelper.isEqual(a, b);
    boolean testB = ReflectiveEqualsHelper.isEqual(a, c);
    boolean testC = ReflectiveEqualsHelper.isEqual(b, c);
    assertTrue(testA);
    assertFalse(testB);
    assertFalse(testC);
}

class Vector {
    public static final int STATIC = 1;

    private Long x;
    private long y;

    public Vector(Long x, long y) {
        super();
        this.x = x;
        this.y = y;
    }

    public Long getX() {
        return x;
    }

    public void setX(Long x) {
        this.x = x;
    }

    public long getY() {
        return y;
    }

    public void setY(long y) {
        this.y = y;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((x == null) ? 0 : x.hashCode());
        result = prime * result + (int) (y ^ (y >>> 32));
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        return ReflectiveEqualsHelper.isEqual(this, obj);
    }
}

祝好,Kevin

5个回答

4

酷,看起来很有趣,你有使用过那个实用类吗?特别是在性能方面有经验吗? - eglobetrotter
是的,我在工作项目中使用它。尽管由于性能和灵活性(也许并非所有领域都适合#equals),我倾向于不使用#reflectionEquals方法,而是使用“标准方式”与#append。当然,使用#append会有一些繁琐的编码,但仍然比手动编写所有那些样板equals检查要方便得多 :) - Uhlen
Google的Guava也有hashCode和toString助手,对于equals我会坚持使用commons EqualsBuilder。如果您正在实现equals,请记住它需要与您的hasCode实现一致。 - Danny Thomas

3
我建议使用Guava的Objects.hashCode。例如:

Guava's Objects.hashCode

public int hashCode() {
     return Objects.hashCode(getX(), getY(), getZ());
}
< p > Objects.equals 方法应该帮助您构建 isEquals 方法。


1
这其实就是 Arrays.hashCode(),所以没必要引入整个 Guava。 - Jeffrey Blattman

3

由于使用反射技术,性能确实是一个大问题,但还有其他问题。

有时您不想使用所有字段。 特别是对于自我引用的结构,这可能会导致潜在的无限递归。


2
这将会太过昂贵。这些方法被调用的频率比你预想的要高得多。不要这样做。相反,使用像 Eclipse、IntelliJ 或 Netbeans 这样的好一点的 IDE,并让它们自动生成 equals()hashCode()。例如,在 Eclipse 中,您可以通过在源代码中的某个位置右键单击 > Source > Generate hashCode and equals 来完成此操作。

alt text


我用Eclipse做这个,对我来说很好用。我只是在考虑更灵活的方式。 - eglobetrotter
1
尝试记住每次添加或删除字段时重新生成这些方法是一个巨大的麻烦。往往会被遗忘,导致难以发现的微妙错误。当然,您关于反射解决方案的成本是正确的。 - Jeffrey Blattman

1

出于性能问题,我改变了使用反射方法的想法。现在我正在使用Apache commons项目的EqualsBuilderHashCodeBuilder实用工具(感谢建议和反馈),因为它们隐藏了方法的复杂性。为了快速生成#equals#hashCode方法,我使用Fast Code Eclipse插件和自定义代码模板:


<template type="EQUALS_AND_HASHCODE_METHOD">
  <variation></variation>
  <variation-field></variation-field>
  <allow-multiple-variation></allow-multiple-variation>
  <class-pattern></class-pattern>
  <allowed-file-extensions>java</allowed-file-extensions>
  <number-required-classes>1</number-required-classes>
  <description>Generates the equals and hashCode method with EqualsBuilder and HashCodeBuilder</description>
  <template-body>
    <![CDATA[
      @Override
      public boolean equals(final Object obj) {
        if (obj == null) {
          return false;
        }
        if (obj == this) {
          return true;
        }
        if (obj.getClass() != getClass()) {
          return false;
        }

        ${class_name} rhs = (${class_name}) obj;
        return new EqualsBuilder().appendSuper(super.equals(obj))
        #foreach ($field in ${fields})
          .append(${field.name}, rhs.${field.name})
        #end
          .isEquals();
      }

      @Override
      public int hashCode() {
        return new HashCodeBuilder(17, 37).appendSuper(super.hashCode())
        #foreach ($field in ${fields})
          .append(${field.name})
        #end
          .toHashCode();
      }
    ]]>
  </template-body>
</template>

我正在使用Fast Code插件,因为它能够获取所选类的所有字段。但是我对插件的可用性不满意。如果Eclipse代码模板引擎也能做到这一点,那就太好了。如果有人知道类似的插件,请告诉我!

谢谢,Kevin


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接