Apache Commons等于/ hashCode构建器


155

我很好奇,这里的人们对使用org.apache.commons.lang.builder EqualsBuilder/ HashCodeBuilder 实施equals/有hashCode什么看法 ?比编写自己的方法更好吗?它与Hibernate配合使用是否很好?你怎么看?


16
只是不要被reflectionEqualsreflectionHashcode函数所吸引;表演绝对是杀手.。
skaffman 2011年

14
我昨天在这里看到了有关平等的讨论,并有一些空闲时间,所以我做了一个快速测试。我有4个具有不同equals实现的对象。eclipse生成,equalsbuilder.append,equalsbuilder.reflection和pojomatic注释。基线是日食。equalsbuilder.append花了3.7倍。pojomatic用了5倍。基于反射的分辨率为25.8倍。这是非常令人沮丧的,因为我喜欢基于反射的简单性,我不能忍受“柔情”这个名字。
digitaljoel 2011年

5
另一种选择是龙目岛计划;它使用字节码生成而不是反射,因此它的性能应与Eclipse生成的一样好。 projectlombok.org/features/EqualsAndHashCode.html
英里

Answers:


212

commons / lang构建器很棒,我使用它们已有多年了,而没有明显的性能开销(有和没有休眠)。但正如Alain所写,番石榴的方法更好:

这是一个示例Bean:

public class Bean{

    private String name;
    private int length;
    private List<Bean> children;

}

这是用Commons / Lang实现的equals()和hashCode():

@Override
public int hashCode(){
    return new HashCodeBuilder()
        .append(name)
        .append(length)
        .append(children)
        .toHashCode();
}

@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return new EqualsBuilder()
            .append(name, other.name)
            .append(length, other.length)
            .append(children, other.children)
            .isEquals();
    } else{
        return false;
    }
}

这里是Java 7或更高版本(受Guava启发):

@Override
public int hashCode(){
    return Objects.hash(name, length, children);
}

@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return Objects.equals(name, other.name)
            && length == other.length // special handling for primitives
            && Objects.equals(children, other.children);
    } else{
        return false;
    }
}

注意:此代码最初引用了Guava,但正如注释所指出的那样,此功能自JDK中引入以来,因此不再需要Guava。

如您所见,Guava / JDK版本较短,并且避免了多余的辅助对象。在相等的情况下,如果更早的Object.equals()调用返回false ,它甚至可以使评估短路(公平地说:commons / lang具有ObjectUtils.equals(obj1, obj2)相同语义的方法,可以用来代替EqualsBuilder上述的短路)。

所以:是的,与手动构建的方法equals()hashCode()方法(或Eclipse将为您生成的那些可怕的怪物)相比,公共语言生成器更可取,但是Java 7+ / Guava版本更好。

还有关于休眠的说明:

在equals(),hashCode()和toString()实现中使用惰性集合时要小心。如果您没有公开的会议,那将惨遭失败。


注意(关于equals()):

a)在上述equals()的两个版本中,您可能还希望使用以下一个或两个快捷方式:

@Override
public boolean equals(final Object obj){
    if(obj == this) return true;  // test for reference equality
    if(obj == null) return false; // test for null
    // continue as above

b)根据您对equals()合约的解释,您还可以更改行

    if(obj instanceof Bean){

    // make sure you run a null check before this
    if(obj.getClass() == getClass()){ 

如果使用第二个版本,则可能还需要super(equals())equals()方法内部进行调用。意见分歧在这里,在这个问题上讨论主题:

将超类合并到Guava Objects.hashcode()实现中的正确方法?

(尽管大约是hashCode(),这也适用于equals()


注意(灵感来自kayahr的评论)

Objects.hashCode(..)Arrays.hashCode(...)如果您有许多原始字段,则(与底层一样)的性能可能会很差。在这种情况下,EqualsBuilder实际上可能是更好的解决方案。


34
同样将有可能与Java 7 Objects.equals:download.oracle.com/javase/7/docs/api/java/util/...
托马斯荣格

3
如果我没看错,Josh Bloch在“ 有效Java ”第8项中说,您不应在equals()方法中使用getClass()。而是应该使用instanceof。
杰夫·奥尔森

6
@SeanPatrickFloyd Guava-way不仅为varargs创建数组对象,还将所有参数转换为对象。因此,当您向其传递10个int值时,最终将得到10个Integer对象和一个数组对象。无论您在哈希码中附加多少值,commons-lang解决方案都只会创建一个对象。与相同的问题equals。番石榴将所有值转换为对象,commons-lang仅创建一个新对象。
kayahr

1
@wonhee我强烈不同意这样会更好。我不会使用反射来计算哈希码。性能开销可能微不足道,但是感觉很不对。
肖恩·帕特里克·弗洛伊德

1
只要您仅在叶类中实现equals(),@ kaushik将类最终定为实际上可以解决这两个版本的潜在问题(instanceof和getClass())
肖恩·帕特里克·弗洛伊德

18

伙计们,醒醒!从Java 7开始,标准库中提供了equalshashCode的辅助方法。它们的用法完全等同于Guava方法的用法。


a)在问这个问题时,Java 7还没有出现b)从技术上讲,它们并不完全相同。jdk具有Objects.equals方法,而Guava具有Objects.equal方法。我只能在Guava版本中使用静态导入。我知道那只是化妆品,但这会使非番石榴变得更加混乱。
肖恩·帕特里克·弗洛伊德

由于Objects.equals将调用实例的.equals方法,因此这不是重写对象equals方法的好方法。如果在实例的.equals方法内调用Objects.equals,则将导致堆栈溢出。
dardo

当它陷入困境时,您能举个例子吗?
Mikhail Golubtsov

OP正在要求重写Object中的equals()方法。根据静态方法Objects.equals()的文档:“如果参数彼此相等,则返回true,否则返回false。因此,如果两个参数都为null,则返回true,如果恰好一个参数为null,则返回false返回值,否则,将使用第一个参数的equals方法确定是否相等。 “因此,如果在重写的实例equals()中使用Objects.equals(),它将调用它自己的equals方法,然后调用Objects.equals()然后再次出现本身,从而导致堆栈溢出。
dardo

@dardo我们正在谈论实现结构相等,这意味着如果两个对象的字段相同,则它们彼此相等。请参阅上面的Guava示例,了解如何实现平等。
Mikhail Golubtsov

8

如果您不想依赖第三方库(也许您正在运行的设备资源有限),甚至不想键入自己的方法,也可以让IDE来完成这项工作,例如在Eclipse中使用

Source -> Generate hashCode() and equals()...

您将获得“本机”代码,可以根据需要对其进行配置,并且必须在更改时提供支持。


示例(Eclipse Juno):

import java.util.Arrays;
import java.util.List;

public class FooBar {

    public String string;
    public List<String> stringList;
    public String[] stringArray;

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((string == null) ? 0 : string.hashCode());
        result = prime * result + Arrays.hashCode(stringArray);
        result = prime * result
                + ((stringList == null) ? 0 : stringList.hashCode());
        return result;
    }
    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        FooBar other = (FooBar) obj;
        if (string == null) {
            if (other.string != null)
                return false;
        } else if (!string.equals(other.string))
            return false;
        if (!Arrays.equals(stringArray, other.stringArray))
            return false;
        if (stringList == null) {
            if (other.stringList != null)
                return false;
        } else if (!stringList.equals(other.stringList))
            return false;
        return true;
    }

}

14
是的,但是Eclipse生成的代码是不可读和不可维护的。
肖恩·帕特里克·弗洛伊德

6
拜托,永远不要想像日食产生的可怕的事情equals。如果您不想依赖第三方库,请像您一样编写单行方法Objects.equal。即使只使用一两次,它也可以使代码变得更好!
maaartinus

@maaartinus equals/ hashCode一种方法
2012年

1
@maaartinus Guava是第3方图书馆。我指出,如果您要使用第三方库避免使用我的解决方案,则可以使用。
FrVaBe

1
@FrVaBe:我写道:“如果您不想依赖第三方库,那么请自己编写像Objects.equal这样的单行方法。” 然后,我编写了一种单行方法,您可以使用Guava来避免这种情况,并且仍然将等长的长度减少一半左右。
maaartinus

6

EqualsBuilder和HashCodeBuilder具有两个主要方面,与手动编写的代码不同:

  • 空处理
  • 实例创建

EqualsBuilder和HashCodeBuilder使比较可能为null的字段变得更加容易。使用手动编写的代码,可以创建许多样板。

另一方面,EqualsBuilder将根据equals方法调用创建一个实例。如果经常调用equals方法,这将创建很多实例。

对于Hibernate,equals和hashCode实现没有区别。它们只是实现细节。对于几乎所有休眠状态下加载的域对象,都可以忽略Builder的运行时开销(即使没有转义分析)。数据库和通信开销将很大。

正如skaffman所提到的,反射版本不能在生产代码中使用。反思将是缓慢的,除了最简单的类,“实现”对于所有的东西都不正确。考虑到所有成员也很危险,因为新引入的成员会更改equals方法的行为。反射版本在测试代码中可能很有用。


我不同意反射实现“除了最简单的类之外,对所有其他东西都不正确”。使用构建器,您可以根据需要显式排除字段,因此实现实际上取决于您的业务键定义。不幸的是,我不能不同意基于反射的实现的性能方面。
digitaljoel

1
@digitaljoel是的,您可以排除字段,但是这些定义不会重构保存。所以我没有故意提及他们。
Thomas Jung


0

如果仅处理id为主键的实体bean,则可以简化。

   @Override
   public boolean equals(Object other)
   {
      if (this == other) { return true; }
      if ((other == null) || (other.getClass() != this.getClass())) { return false; }

      EntityBean castOther = (EntityBean) other;
      return new EqualsBuilder().append(this.getId(), castOther.getId()).isEquals();
   }

0

在我看来,它与Hibernate搭配使用效果不佳,尤其是答案比较了某些实体的长度,名称和子代的示例。Hibernate建议在equals()和hashCode()中使用业务密钥,这有其原因。如果在业务密钥上使用自动equals()和hashCode()生成器,则可以,只需考虑性能问题即可,如前所述。但是人们通常使用所有属性,这是IMO非常错误的。例如,我目前正在从事使用Pojomatic和@AutoProperty编写实体的项目,我认为这是一种非常糟糕的模式。

使用hashCode()和equals()的两个主要方案是:

  • 当您将持久性类的实例放入Set中(表示多值关联的推荐方法)时,
  • 使用分离实例的重新连接时

因此,假设我们的实体如下所示:

class Entity {
  protected Long id;
  protected String someProp;
  public Entity(Long id, String someProp);
}

Entity entity1 = new Entity(1, "a");
Entity entity2 = new Entity(1, "b");

两者对于Hibernate都是相同的实体,它们是从某个会话的某个时刻获取的(它们的id和class / table是相等的)。但是,当我们在所有道具上实现自动equals()hashCode()时,我们拥有什么呢?

  1. 当您将entity2放入已存在entity1的持久集时,它将被放置两次,并在提交期间导致异常。
  2. 如果要将分离的entity2附加到已存在entity1的会话,则它们(可能是我没有对此进行特别测试)将无法正确合并。

因此,对于我完成的99%的项目,我们使用在基本实体类中一次编写的equals()和hashCode()的以下实现,这与Hibernate概念一致:

@Override
public boolean equals(Object obj) {
    if (StringUtils.isEmpty(id))
        return super.equals(obj);

    return getClass().isInstance(obj) && id.equals(((IDomain) obj).getId());
}

@Override
public int hashCode() {
    return StringUtils.isEmpty(id)
        ? super.hashCode()
        : String.format("%s/%s", getClass().getSimpleName(), getId()).hashCode();
}

对于瞬态实体,我所做的与Hibernate在持久性步骤中所做的相同,即。我使用实例匹配。对于持久性对象,我比较了唯一键,即表/ ID(我从不使用复合键)。


0

为了以防万一,其他人会发现它很有用,我想出了一个用于哈希代码计算的Helper类,该类避免了上面提到的额外的对象创建开销(实际上,Objects.hash()方法的开销甚至更大)继承,因为它将在每个级别上创建一个新数组!)。

用法示例:

public int hashCode() {
    return HashCode.hash(HashCode.hash(timestampMillis), name, dateOfBirth); // timestampMillis is long
}

public int hashCode() {
    return HashCode.hash(super.hashCode(), occupation, children);
}

HashCode助手:

public class HashCode {

    public static int hash(Object o1, Object o2) {
        return add(Objects.hashCode(o1), o2);
    }

    public static int hash(Object o1, Object o2, Object o3) {
        return hash(Objects.hashCode(o1), o2, o3);
    }

    ...

    public static int hash(Object o1, Object o2, ..., Object o10) {
        return hash(Objects.hashCode(o1), o2, o3, ..., o10);
    }

    public static int hash(int initial, Object o1, Object o2) {
        return add(add(initial, o1), o2);
    }

    ...

    public static int hash(int initial, Object o1, Object o2, ... Object o10) {
        return add(... add(add(add(initial, o1), o2), o3) ..., o10);
    }

    public static int hash(long value) {
        return (int) (value ^ (value >>> 32));
    }

    public static int hash(int initial, long value) {
        return add(initial, hash(value));
    }

    private static int add(int accumulator, Object o) {
        return 31 * accumulator + Objects.hashCode(o);
    }
}

我认为域模型中属性的最大合理数量是10,如果您有更多的属性,则应考虑重构和引入更多的类,而不是维护字符串和基元的堆。

缺点是:如果您主要需要深度散列的基元和/或数组,则它没有用。(通常,当您必须处理无法控制的平面(传输)对象时,就是这种情况)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.