2012-06-06 29 views
22

我经常使用apache HashCodeBuilder和EqualsBuilder作为使用反射的对象平等,但最近我有一位同事告诉我如果实体包含很多属性,使用反射可能会导致巨大的性能下降。担心我可能会使用错误的实现,我的问题是,您更喜欢以下哪种方法?为什么?HashCodeBuilder和EqualsBuilder使用风格

public class Admin { 

    private Long id; 
    private String userName; 

    public String getUserName() { 
     return userName; 
    } 

    @Override 
    public boolean equals(Object o) { 
     if (!(o instanceof Admin)) { 
      return false; 
     } 
     Admin otherAdmin = (Admin) o; 
     EqualsBuilder builder = new EqualsBuilder(); 
     builder.append(getUserName(), otherAdmin.getUserName()); 
     return builder.isEquals(); 
    } 

    @Override 
    public int hashCode() { 
     HashCodeBuilder builder = new HashCodeBuilder(); 
     builder.append(getUserName()); 
     return builder.hashCode(); 
    } 
} 

比。

public class Admin { 

    private Long id; 
    private String userName; 

    public String getUserName() { 
     return userName; 
    } 

    @Override 
    public boolean equals(Object o) { 
     return EqualsBuilder.reflectionEquals(this, o, Arrays.asList(id)); 
    } 

    @Override 
    public int hashCode() { 
     return HashCodeBuilder.reflectionHashCode(this, Arrays.asList(id)); 
    } 
} 
+0

你需要接受下面那个套件的答案之一来回答你的问题。我也有同样的问题,不能证明下面的答案是哪一种仪式。尝试接受任何答案。 – developer

+2

他为什么需要接受下面的答案之一?如果问题没有得到回答,那么为什么接受。我会说让它开放,直到有人给出了一个很好的答案。 – Churk

回答

9

当然,第二个选项更优雅和简单。但是如果您关心性能,应该采取第一种方法,如果安全管理器正在运行,则第二种方法也会失败。

如果我处于您的情况,我会选择第一个选项。 另外你的第一种方法在生成hashCode 时出现错误,应该返回builder.toHashCode(); 而不是返回builder.hashCode();(返回哈希码生成器对象哈希码)

+1

我可以使用hashCode()而不是toHashCode(),根据HashCodeBuilder的更新实现,hashCode()方法现在调用toHashCode()http://commons.apache.org/lang/api-2.6/index.html? org/apache/commons/lang/builder/HashCodeBuilder.html – tintin

+2

有趣的事实:EqualsBuilder.equals()不调用EqualsBuilder.isEquals()。这不是一个不好的陷阱?! : -/ –

9

我宁愿第二个选项有两个原因:

  1. 显然,这是更容易阅读

  2. 我不会为第一种选择购买性能参数,除非那些参数包含相关指标。 例如基于反射的“等于”会多少毫秒增加到典型的端到端请求延迟?总的来说,这会增加多少%? 不知道的好机会,优化是写清楚过早

3

你的问题说明了第二种方法的好处之一:

在第一种情况下,它的非常容易使错误return builder.hashCode(),而不是正确的return builder.toHashCode(),这将导致可能非常难以追查的细微错误。

第二种情况消除了这种打字错误的可能性,导致您的头部撞击键盘的尝试发现错误的可能性更小。

+0

对于Apache Commons v2.5或更新版本,此评论不正确,Apache Commons v2.5于2010年2月发布。引用javadoc:“toHashCode()计算出的hashCode由于可能存在错误错误地调用了toHashCode()以及它对HashCodeBuilder本身的哈希代码是什么的不可能性。“ javadoc可以在这里找到:http://commons.apache.org/proper/commons-lang/release-history.html – cfogelberg

+0

很高兴知道这个bug已经修复。答案仍然与卡住旧版图书馆的人有关(虽然已经有几年了,人们应该更新,但是有很多“企业”的地方在这类事情上很慢) – Krease

2

我会说这些都不是很好的实施。我认为EqualsBuilder不是一个好的框架,因为以下原因:

  1. 不可扩展。如果你试图断言平等的领域之一应该将零和空白视为平等?
  2. 您必须保持变量列表,就好像它们是硬编码变量。这意味着你必须列出你想要比较的所有变量。此时,您已经指出了使用反射会占用额外资源的问题,并且在企业中压缩数十亿个对象的应用程序。做这个反射等于跟内存泄漏一样糟糕。

我会说需要一个比Apache更好的框架。

+0

1。确保属性是NULL或空字符串(例如在setter中)。问题解决了。 –

10

即使第二个选项更具吸引力(因为它只是一行代码),我会选择第一个选项。

原因很简单,就是表现。在运行一个小测试后,我发现它们之间有很大的时间差异。

为了排序得到的时间的想法,我创造了这个两个简单的类:

package equalsbuildertest; 

import java.math.BigDecimal; 
import java.util.Date; 

import org.apache.commons.lang3.builder.EqualsBuilder; 
import org.apache.commons.lang3.builder.HashCodeBuilder; 

public class Class1 { 

    private int field1; 

    private boolean field2; 

    private BigDecimal field3; 

    private String field4; 

    private Date field5; 

    private long field6; 

    public Class1(int field1, boolean field2, BigDecimal field3, String field4, 
      Date field5, long field6) { 
     super(); 
     this.field1 = field1; 
     this.field2 = field2; 
     this.field3 = field3; 
     this.field4 = field4; 
     this.field5 = field5; 
     this.field6 = field6; 
    } 

    public Class1() { 
     super(); 
    } 

    public int getField1() { 
     return field1; 
    } 

    public void setField1(int field1) { 
     this.field1 = field1; 
    } 

    public boolean isField2() { 
     return field2; 
    } 

    public void setField2(boolean field2) { 
     this.field2 = field2; 
    } 

    public BigDecimal getField3() { 
     return field3; 
    } 

    public void setField3(BigDecimal field3) { 
     this.field3 = field3; 
    } 

    public String getField4() { 
     return field4; 
    } 

    public void setField4(String field4) { 
     this.field4 = field4; 
    } 

    public Date getField5() { 
     return field5; 
    } 

    public void setField5(Date field5) { 
     this.field5 = field5; 
    } 

    public long getField6() { 
     return field6; 
    } 

    public void setField6(long field6) { 
     this.field6 = field6; 
    } 

    @Override 
    public boolean equals(Object o) { 
     return EqualsBuilder.reflectionEquals(this, o); 
    } 

    @Override 
    public int hashCode() { 
     return HashCodeBuilder.reflectionHashCode(this); 
    } 

} 

和:

package equalsbuildertest; 

import java.math.BigDecimal; 
import java.util.Date; 

import org.apache.commons.lang3.builder.EqualsBuilder; 
import org.apache.commons.lang3.builder.HashCodeBuilder; 

public class Class2 { 

    private int field1; 

    private boolean field2; 

    private BigDecimal field3; 

    private String field4; 

    private Date field5; 

    private long field6; 

    public Class2(int field1, boolean field2, BigDecimal field3, String field4, 
      Date field5, long field6) { 
     super(); 
     this.field1 = field1; 
     this.field2 = field2; 
     this.field3 = field3; 
     this.field4 = field4; 
     this.field5 = field5; 
     this.field6 = field6; 
    } 

    public Class2() { 
     super(); 
    } 

    public int getField1() { 
     return field1; 
    } 

    public void setField1(int field1) { 
     this.field1 = field1; 
    } 

    public boolean isField2() { 
     return field2; 
    } 

    public void setField2(boolean field2) { 
     this.field2 = field2; 
    } 

    public BigDecimal getField3() { 
     return field3; 
    } 

    public void setField3(BigDecimal field3) { 
     this.field3 = field3; 
    } 

    public String getField4() { 
     return field4; 
    } 

    public void setField4(String field4) { 
     this.field4 = field4; 
    } 

    public Date getField5() { 
     return field5; 
    } 

    public void setField5(Date field5) { 
     this.field5 = field5; 
    } 

    public long getField6() { 
     return field6; 
    } 

    public void setField6(long field6) { 
     this.field6 = field6; 
    } 

    @Override 
    public boolean equals(Object obj) { 
     if (!(obj instanceof Class2)) { 
      return false; 
     } 
     Class2 other = (Class2) obj; 
     EqualsBuilder builder = new EqualsBuilder(); 
     builder.append(field1, other.field1); 
     builder.append(field2, other.field2); 
     builder.append(field3, other.field3); 
     builder.append(field4, other.field4); 
     builder.append(field5, other.field5); 
     builder.append(field6, other.field6); 
     return builder.isEquals(); 
    } 

    @Override 
    public int hashCode() { 
     HashCodeBuilder builder = new HashCodeBuilder(); 
     builder.append(getField1()); 
     builder.append(isField2()); 
     builder.append(getField3()); 
     builder.append(getField4()); 
     builder.append(getField5()); 
     builder.append(getField6()); 
     return builder.hashCode(); 

    }; 

} 

这第二类是几乎一样的第一个,但有不同的equals和hashCode。

在那之后,我创建了以下测试:

package equalsbuildertest; 

import static org.junit.Assert.*; 

import java.math.BigDecimal; 
import java.util.Date; 

import org.junit.Test; 

public class EqualsBuilderTest { 

    @Test 
    public void test1() { 
     Class1 class1a = new Class1(1, true, new BigDecimal(0), "String", new Date(), 1L); 
     Class1 class1b = new Class1(1, true, new BigDecimal(0), "String", new Date(), 1L); 
     for (int i = 0; i < 1000000; i++) { 
      assertEquals(class1a, class1b); 
     } 
    } 

    @Test 
    public void test2() { 
     Class2 class2a = new Class2(1, true, new BigDecimal(0), "String", new Date(), 1L); 
     Class2 class2b = new Class2(1, true, new BigDecimal(0), "String", new Date(), 1L); 
     for (int i = 0; i < 1000000; i++) { 
      assertEquals(class2a, class2b); 
     } 
    } 

} 

测试非常简单,只用于测量时间。

的结果如下:

  • TEST1(2024多个)
  • TEST2(0039多个)

我选择他们是为了具有完全相等最伟大的时代。如果您选择使用NotEquals条件进行测试,则时间会更短,但保持非常大的时间差。

我使用Fedora 21和Eclipse Luna在64位Intel Core i5-3317U CPU @ 1.70GHz x4上运行此测试。总之,为了节省几行代码,你可能无法用模板打字(在Windows下的Eclipse - >首选项在Java中找到 - >>),我不会冒险有如此出色的性能差异。编辑器 - >模板)如下:

${:import(org.apache.commons.lang3.builder.HashCodeBuilder, org.apache.commons.lang3.builder.EqualsBuilder)} 
@Override 
public int hashCode() { 
    HashCodeBuilder hashCodeBuilder = new HashCodeBuilder(); 
    hashCodeBuilder.append(${field1:field}); 
    hashCodeBuilder.append(${field2:field}); 
    hashCodeBuilder.append(${field3:field}); 
    hashCodeBuilder.append(${field4:field}); 
    hashCodeBuilder.append(${field5:field}); 
    return hashCodeBuilder.toHashCode(); 
} 

@Override 
public boolean equals(Object obj) { 
    if (this == obj) { 
     return true; 
    } 
    if (obj == null) { 
     return false; 
    } 
    if (getClass() != obj.getClass()) { 
     return false; 
    } 
    ${enclosing_type} rhs = (${enclosing_type}) obj; 
    EqualsBuilder equalsBuilder = new EqualsBuilder(); 
    equalsBuilder.append(${field1}, rhs.${field1}); 
    equalsBuilder.append(${field2}, rhs.${field2}); 
    equalsBuilder.append(${field3}, rhs.${field3}); 
    equalsBuilder.append(${field4}, rhs.${field4}); 
    equalsBuilder.append(${field5}, rhs.${field5});${cursor} 
    return equalsBuilder.isEquals(); 
} 
1

同意@Churk,Apache HashCodeBuilder和EqualsBuilder没有很好地实现。 HashCodeBuilder仍在玩素数!此外,它做了很多不必要的工作。你有没有读过源代码?

由于Java 5(如果不是更早),AbstractHashMap <>没有使用质数模的模来定位哈希桶。相反,桶的数量是2的幂,散列码的低位N比特用于定位桶。

此外,它将“混合”应用程序提供的哈希码,以便均匀分散比特位,从而均匀填充桶。

因此,重写int Object.hashCode()的正确方法是通过返回最简单的常量值,使用该类在对象群中返回任何集合

通常,ID值未修改是您最好的选择。如果您的ID字段是整数,只需将其转换为(int)并将其返回。如果它是一个String或其他对象,只需返回它的哈希码。你明白了。对于化合物标识符,返回其中具有最多不同值的字段(或其hashCode)。少即是多。

当然,必须满足hashCode()和equals()之间的约定。因此,equals()应该相应地执行。 hashCode()不需要使用等式所需的完全限定符,但在equals()中必须使用hashCode()中使用的任何字段。在这里,像StringUtils.equals(s1,s2)这样的方法对于一致且安全地处理空值非常有用。

+0

更重要的是:HashCodeBuilder.reflectionHashCode()和EqualsBuilder.reflectionEquals()实际上有点危险,因为它们很容易违反equals()/ hashCode()合约的“一致”部分。这里发生了什么:如果一个对象被放置在某种类型的散列表/集合中,并且随后在hashCode()或equals()中使用的字段被更改,则该对象在集合中被有效孤立,并且永远不会被访问。 hashCode()或equals()中只应使用不变的标识符字段。开发人员不能将此选择留给通用的“构建器”。 – Charlie

+0

将对象插入集合后更改字段是任何equals/hash实现的问题 – Bax