首页 文章

Apache Commons equals / hashCode builder [关闭]

提问于
浏览
151

我很想知道,人们在这里想到使用 org.apache.commons.lang.builder EqualsBuilder / HashCodeBuilder 来实现 equals / hashCode ?这比写自己更好吗?它与Hibernate相处得好吗?你怎么看?

8 回答

  • 8

    公共/ lang建设者是伟大的,我已经使用它们多年没有明显的性能开销(有和没有休眠) . 但正如阿兰所写, 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,但正如评论所指出的那样,此功能已经在JDK中引入,因此不再需要Guava .

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

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

    关于Hibernate的说明:

    注意在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() Contract 的解释,您也可以更改线路

    if(obj instanceof Bean){
    

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

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

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

    (虽然它大约是 hashCode() ,但同样适用于 equals()


    Note (inspired by Comment from kayahr)

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

  • 6

    伙计们,醒醒! Since Java 7 标准库中有equalshashCode的辅助方法 . 它们的用法完全等同于 Guava 方法的使用 .

  • 14

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

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

    您将获得'native'代码,您可以根据需要配置这些代码以及必须支持更改的代码 .


    示例(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;
        }
    
    }
    
  • 0

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

    • null处理

    • 实例创建

    EqualsBuilder和HashCodeBuilder使得比较可能为null的字段变得更容易 . 使用手动编写的代码,这会创建大量的样板 .

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

    对于Hibernate,equals和hashCode实现没有区别 . 它们只是一个实现细节 . For almost all domain objects loaded with hibernate the runtime overhead (even without escape analysis) of the Builder can be ignored . 数据库和通信开销将是重要的 .

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

  • 0

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

  • 207

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

    在我看来,它不适合Hibernate,尤其是比较长度,名称和某些实体的子项的答案中的示例 . Hibernate建议在equals()和hashCode()中使用to use business key,它们有其原因 . 如果你使用auto equals()和它是你的业务键上的hashCode()生成器,它正在使用带有@AutoProperty的Pojomatic编写实体的项目,我认为这是一个非常糟糕的模式 .

    他们使用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相等) . 但是当我们在所有道具上实现auto equals()一个hashCode()时,我们有什么?

    • 当您将entity2放入entity1已经存在的持久集时,这将被放置两次,并且在提交期间将导致异常 .

    • 如果要将分离的实体2附加到会话中,其中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是域模型中最大合理数量的属性,如果你有更多,你应该考虑重构和引入更多类而不是维护一堆字符串和基元 .

    缺点是:如果你需要深入散列的主要原语和/或数组,它就没用了 . (通常情况下,当您必须处理不受控制的平面(转移)对象时) .

相关问题