Generic reflective helper method for equals and hashCode

亡梦爱人 提交于 2019-12-13 14:26:34

问题


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


回答1:


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.




回答2:


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.




回答3:


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.




回答4:


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.




回答5:


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



来源:https://stackoverflow.com/questions/4417035/generic-reflective-helper-method-for-equals-and-hashcode

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!