【问题标题】:Apache Commons equals/hashCode builder [closed]Apache Commons equals/hashCode builder [关闭]
【发布时间】:2011-06-29 14:54:08
【问题描述】:

我很想知道这里的人们对使用的看法 org.apache.commons.lang.builderEqualsBuilder/HashCodeBuilder 实现equals/hashCode?这会比自己编写更好吗?它与 Hibernate 配合得好吗?你有什么意见?

【问题讨论】:

  • 不要被reflectionEqualsreflectionHashcode 函数所诱惑;性能绝对是杀手锏。
  • 我昨天看到这里有一些关于equals的讨论,有一些空闲时间,所以我做了一个快速测试。我有 4 个具有不同 equals 实现的对象。 eclipse 生成、equalsbuilder.append、equalsbuilder.reflection 和 pojomatic 注释。基线是日食。 equalsbuilder.append 花了 3.7 倍。 pojomatic 花了 5 倍。基于反射的时间为 25.8 倍。这很令人沮丧,因为我喜欢基于反射的简单性,我无法忍受“pojomatic”这个名字。
  • 另一个选项是 Project Lombok;它使用字节码生成而不是反射,因此它的性能应该与 Eclipse 生成的一样好。 projectlombok.org/features/EqualsAndHashCode.html

标签: java hibernate equals apache-commons hashcode


【解决方案1】:

commons/lang 构建器非常棒,我多年来一直在使用它们,而没有明显的性能开销(有和没有休眠)。但正如 Alain 所写,Guava 方式更好:

这是一个示例 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,但正如 cmets 所指出的,该功能已经在 J​​DK 中引入,因此不再需要 Guava。

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

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

还有关于 Hibernate 的说明:

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


注意(关于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()){ 

如果您使用第二个版本,您可能还想在您的equals() 方法中调用super(equals())。这里意见不一,这个话题在这个问题中讨论:

right way to incorporate superclass into a Guava Objects.hashcode() implementation?

(虽然是关于hashCode(),但同样适用于equals()


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

Objects.hashCode(..)(就像底层的Arrays.hashCode(...))如果有很多原始字段,可能会表现不佳。在这种情况下,EqualsBuilder 实际上可能是更好的解决方案。

【讨论】:

  • Java 7 Objects.equals 也可以做到这一点:download.oracle.com/javase/7/docs/api/java/util/…
  • 如果我没看错的话,Josh Bloch 在 Effective Java 第 8 项中说,你不应该在 equals() 方法中使用 getClass();相反,您应该使用 instanceof。
  • @SeanPatrickFloyd Guava 方式不仅为可变参数创建一个数组对象,它还将所有参数转换为对象。因此,当您将 10 个 int 值传递给它时,您最终将得到 10 个 Integer 对象和一个数组对象。 commons-lang 解决方案只创建一个对象,无论您将多少值附加到哈希码。 equals 也有同样的问题。 Guava 将所有值转换为对象,commons-lang 只创建一个新对象。
  • @wonhee 我强烈反对这样更好。使用反射来计算哈希码是我永远不会做的事情。性能开销可能可以忽略不计,但感觉不对。
  • @kaushik 使类 final 实际上解决了两个版本(instanceof 和 getClass())的潜在问题,只要您仅在叶类中实现 equals()
【解决方案2】:

各位,醒醒吧! 从 Java 7 开始在标准库中有 equalshashCode 的辅助方法。它们的用法完全等同于 Guava 方法的用法。

【讨论】:

  • a) 在提出这个问题时,Java 7 还不存在 b) 从技术上讲,它们并不完全等同。 jdk 有 Objects.equals 方法与 Guava 的 Objects.equal 方法。我只能在 Guava 的版本中使用静态导入。这只是化妆品,我知道,但它使非番石榴明显更加混乱。
  • 这不是覆盖对象 equals 方法的好方法,因为 Objects.equals 将调用实例的 .equals 方法。如果在实例的 .equals 方法中调用 Objects.equals 会导致堆栈溢出。
  • 你能举个例子,当它陷入循环吗?
  • OP 要求覆盖 Object 中的 equals() 方法。根据静态方法 Objects.equals() 的文档:“如果参数彼此相等则返回 true,否则返回 false。因此,如果两个参数都为 null,则返回 true,如果恰好一个参数为 null,则返回 false 否则,相等性是通过使用第一个参数的 equals 方法来确定的。" 因此,如果您在被覆盖的实例 equals() 中使用 Objects.equals(),它会调用它自己的 equals 方法,然后是 Objects.equals() ,然后是自身,导致堆栈溢出。
  • @dardo 我们正在讨论实现结构相等,因此这意味着如果两个对象的 字段 相等,则它们彼此相等。参见上面的 Guava 示例,equals 是如何实现的。
【解决方案3】:

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

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

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


示例(日食朱诺):

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

}

【讨论】:

  • 没错,但是Eclipse生成的代码不可读,不可维护。
  • 拜托,永远不要考虑像 eclipse 生成的 equals 这样可怕的事情。如果你不想依赖 3rd 方库,那就自己写Objects.equal 这样的单行方法。即使只使用一两次,它也会让代码变得更好!
  • @maaartinus equals/hashCode 一行方法???
  • @maaartinus Guava 是第 3 方库。我指出,如果您想避免使用 3rd 方库,可以使用我的解决方案。
  • @FrVaBe:我写了“如果你不想依赖 3rd 方库,那就自己写 Objects.equal 这样的单行方法。”然后我写了一个你可以用来避免使用 Guava 的单行方法,仍然将 equals 的长度减少到一半左右。
【解决方案4】:

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

  • 空值处理
  • 实例创建

EqualsBuilder 和 HashCodeBuilder 可以更轻松地比较可能为空的字段。使用手动编写的代码,这会创建很多样板。

另一方面,EqualsBuilder 将为每个 equals 方法调用创建一个实例。如果你的 equals 方法经常被调用,这将创建很多实例。

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

正如 skaffman 所说,反射版本不能用于生产代码。反射会变慢,并且除了最简单的类之外,“实现”不会对所有类都是正确的。考虑所有成员也是危险的,因为新引入的成员会改变 equals 方法的行为。反射版本在测试代码中很有用。

【讨论】:

  • 我不同意反射实现“除了最简单的类之外的所有类都不正确”。使用构建器,您可以根据需要明确排除字段,因此实现实际上取决于您的业务密钥定义。不幸的是,我不能不同意基于反射的实现的性能方面。
  • @digitaljoel 是的,您可以排除字段,但这些定义不是重构保存。所以我没有故意提到它们。
【解决方案5】:

如果你不自己写,也可以使用google guava (formerly google collections)

【讨论】:

    【解决方案6】:

    如果你只是处理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();
       }
    

    【讨论】:

      【解决方案7】:

      在我看来,它不能很好地与 Hibernate 配合使用,尤其是答案中比较某些实体的长度、名称和子项的示例。 Hibernate 建议 to use business key 在 equals() 和 hashCode() 中使用,他们有他们的理由。如果您在业务密钥上使用 auto 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 和类/表是相等的)。但是当我们在所有 props 上实现 auto 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 在持久性步骤中所做的相同的操作,即。我使用实例匹配。对于持久对象,我比较唯一键,即 table/id(我从不使用复合键)。

      【讨论】:

        【解决方案8】:

        以防万一,其他人会发现它有用,我想出了这个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 是域模型中属性的最大合理数量,如果你有更多,你应该考虑重构和引入更多类,而不是维护一堆字符串和原语。

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

        【讨论】:

          猜你喜欢
          • 2017-04-27
          • 2016-07-23
          • 1970-01-01
          • 2015-09-12
          • 1970-01-01
          • 2010-11-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多