3

I'm thinking about to create a reflective helper method for equals and hashCode.

  • In case of equals the helper method looks over the reflection API to the fields of objectA and compares them with fields of objectB.
  • In case of hashCode the helper method looks over the reflection API to the fields and calculates a hashCode in a iteration loop.

The good thing is that I don't to be worry about missing fields in my equals or hashCode implementation. The bad thing is I guess performance. What do you think about this idea? Please share your opinion!

This is my first draft for 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);
    }
}

}

Cheers, Kevin

eglobetrotter
  • 692
  • 4
  • 10
  • 23

5 Answers5

4

Take a look at EqualsBuilder in Apache Commons and it's reflectionEquals methods. This library also have a HashCodeBuilder + a lot of other useful stuff.

Uhlen
  • 1,748
  • 14
  • 29
  • Cool looks interesting, do you have experience with that utility class? Especially with performance? – eglobetrotter Dec 11 '10 at 14:06
  • Yes, I use it for projects at my work. Though, because of performance and flexibility (maybe not all fields is suitable for #equals) I tend not to use the #reflectionEquals method but instead the "standard way" with #append. Sure, there's some tedious coding with #append but still a lot more convenient than writing all that boiler plate equals checking for hand :) – Uhlen Dec 11 '10 at 14:12
  • Google's Guava also has a hashCode and toString helper, for equals I'd stick to commons EqualsBuilder. If you're implementing equals remember it needs to be consistent with your hasCode implementation. – Danny Thomas Dec 11 '10 at 14:44
3

Performance is certainly a big concern because of the use of reflection, but there are others.

Sometimes you don't want to use all the fields. Particularly with a self-referential structure, this could lead to a potentially infinite recursion.

Don Roby
  • 39,169
  • 6
  • 84
  • 105
3

I would suggest Guava's Objects.hashCode. For example:

public int hashCode() {
     return Objects.hashCode(getX(), getY(), getZ());
}

The Objects.equals method should help you build your isEquals method.

Brent Wilson
  • 215
  • 2
  • 3
2

This is going to be too expensive. Those methods are called much more often than you would expect. Don't do it. Rather use a bit decent IDE like Eclipse, IntelliJ or Netbeans and have them to autogenerate the equals() and hashCode(). In Eclipse for example you can do it by rightclick somewhere in source code > Source > Generate hashCode and equals.

alt text

BalusC
  • 992,635
  • 352
  • 3,478
  • 3,452
  • I do that with the eclipse and it works fine for me I was just thinking about more flexible way. – eglobetrotter Dec 11 '10 at 14:03
  • 1
    huge PITA to *try* and remember to re-generate those methods every time you add or remove a field. more often than not, folks will forget leading to subtle, hard to find bugs. you are of course right about the expense of the reflective solution. – Jeffrey Blattman Jan 15 '13 at 18:41
1

I changed my mind to use the reflective approach because of the performance issue. Now I'm using the EqualsBuilder and the HashCodeBuilder utilities of the Apache commons project (Thanks for the suggestions and feedback), because they hides the complexity of the methods. For quick generation of the #equals and #hashCode method I use the Fast Code Eclipse plugin with a customized code template:


<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>

I'm using Fast Code plugin because it's able to grap all fields of a selected class. But I'm not happy with the usability of the plugin. It would be nice if eclipse code templating engine would be able to do that too. If anyone knows a similar plugin, then let me know please!

Cheers, Kevin

eglobetrotter
  • 692
  • 4
  • 10
  • 23