Apache Commons等于/ hashCode构建器

时间:2011-02-18 06:10:15

标签: java hibernate equals apache-commons hashcode

我很想知道,这里有人在想什么 org.apache.commons.lang.builder EqualsBuilder / HashCodeBuilder 实施equals / hashCode?这比写自己更好吗?它与Hibernate相处得好吗?你有什么看法?

8 个答案:

答案 0 :(得分:210)

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

以下是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版本更短,避免了多余的帮助对象。在equals的情况下,如果较早的Object.equals()调用返回false,它甚至允许短路评估(公平:commons / lang具有相同语义的ObjectUtils.equals(obj1, obj2)方法,可以用来代替EqualsBuilder允许短路,如上所述。)

所以:是的,公共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()合约的解释,你也可以改变这些行

    if(obj instanceof Bean){

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

如果您使用的是第二个版本,则可能还需要在super(equals())方法中调用equals()。意见不同,这个问题在这个问题中讨论:

  

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

(虽然约为hashCode(),但同样适用于equals()


注意(受kayahr评论的启发)

如果您有许多原始字段,

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

答案 1 :(得分:15)

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

答案 2 :(得分:8)

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

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

}

答案 3 :(得分:6)

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

  • null handling
  • 实例创建

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

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

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

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

答案 4 :(得分:4)

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

答案 5 :(得分: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();
   }

答案 6 :(得分:0)

在我看来,它不适合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和class / table相等)。但是当我们在所有道具上实现auto equals()一个hashCode()时,我们有什么?

  1. 当你将entity2放到entity1已经存在的持久集中时,这将被放置两次并在提交期间导致异常。
  2. 如果要将分离的实体2附加到会话中,其中entity1已经存在,那么它们(可能是我没有特别测试过)将无法正确合并。
  3. 因此,对于我制作的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(我从不使用复合键)。

答案 7 :(得分: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是域模型中最大合理的属性数,如果你有更多,你应该考虑重构和引入更多的类而不是维护一堆字符串和基元。

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