2010-12-11 64 views
3

我正在考慮爲equals和hashCode創建反射助手方法。equals和hashCode的泛型反射助手方法

  • 在等號的情況下,輔助方法將反射API查找objectA的字段並將它們與objectB的字段進行比較。
  • 在hashCode的情況下,幫助器方法將反射API查找字段並在迭代循環中計算hashCode。

好的是我不必擔心在我的equals或hashCode實現中缺少字段。 壞事是我猜測性能。你對這個想法有什麼看法?請分享你的意見!

這是我第一次平等草案:

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

}

歡呼聲中,凱文

回答

2

這將是太貴了。這些方法被調用的次數比您預期的要多得多。不要這樣做。而是使用像Eclipse,IntelliJ或Netbeans等體面的IDE,並讓它們自動生成equals()hashCode()。在Eclipse中,例如,您可以通過右鍵單擊源代碼中的某處>Source>生成hashCode並等於

alt text

+0

我這樣做與日食和它對我工作正常我只是想更靈活的方式。 – eglobetrotter 2010-12-11 14:03:53

+0

巨大的PITA *嘗試*,並記住每次添加或刪除字段時重新生成這些方法。通常情況下,人們會忘記導致微妙,難以發現的錯誤。你當然是對付反射解決方案的代價。 – 2013-01-15 18:41:14

4

看看在Apache CommonsEqualsBuilder和它的reflectionEquals方法。這個庫也有一個HashCodeBuilder +很多其他有用的東西。

+0

酷看起來很有意思,你有這種實用課程的經驗嗎?特別是有性能? – eglobetrotter 2010-12-11 14:06:27

+0

是的,我在工作中將它用於項目。雖然,由於性能和靈活性(也許不是所有的字段都適用於#equals),我傾向於不使用#reflectionEquals方法,而是使用#append的「標準方式」。當然,使用#append有一些繁瑣的編碼,但比編寫所有的鍋爐板等於檢查手還要方便:) – Uhlen 2010-12-11 14:12:33

+0

谷歌的番石榴也有一個hashCode和toString助手,相當於我會堅持Commons EqualsBuilder。如果你正在實現equals,記住它需要與你的hasCode實現一致。 – 2010-12-11 14:44:19

2

由於使用了反射,性能肯定是一個大問題,但也有其他的。

有時你不想使用所有的字段。特別是在自引用結構的情況下,這可能導致潛在的無限遞歸。

+0

遞歸是一個很好的觀點,謝謝! – eglobetrotter 2010-12-11 14:08:50

3

我會建議Guava's Objects.hashCode。例如:

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

Objects.equals方法可以幫助你建立你的isEquals方法。

+1

......這只不過是'Arrays.hashCode()',所以沒有理由牽扯到所有的番石榴。 – 2013-01-15 18:33:47

1

由於性能問題,我改變了使用反射方法的想法。現在我使用Apache commons項目的EqualsBuilderHashCodeBuilder實用程序(感謝您的建議和反饋),因爲它們隱藏了方法的複雜性。爲了快速生成#equals和方法我用的是Fast Code Eclipse plugin用定製的代碼模板:


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

我用快速的代碼的插件,因爲它能夠虎視眈眈選定類的所有領域。但是我對插件的可用性不滿意。如果eclipse代碼模板引擎也能做到這一點,那將會很好。如果有人知道一個類似的插件,請讓我知道!

乾杯,凱文