Apache Commons equals/hashCode Builder Apache Commons 等于/hashCode Builder/hashCode Builder

我很好奇,这里的人们是怎么看待使用 org.apache.commons.lang.builder EqualsBuilder/HashCodeBuilder 实施 equals/hashCode?这比你自己写好吗?它和 Hibernate 配合得好吗?你怎么看?

82684 次浏览

如果你不写你自己的,也有可能使用 谷歌番石榴(原谷歌收藏)

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

  • 空处理
  • 实例创建

EqualsBuilder 和 HashCodeBuilder 使得比较可能为空的字段变得更加容易。使用手工编写的代码,这将创建大量的样板代码。

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

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

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

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

这里是 Java7或更高版本(灵感来自番石榴) :

@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 版本更短,避免了多余的 helper 对象。在等于的情况下,如果早期的 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 ()契约的解释,您还可以更改这一行

    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())


备注(灵感来自 卡亚的评论)

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

如果你不想依赖于第三方库(也许你正在运行一个资源有限的设备) ,你甚至不想输入你自己的方法,你也可以让 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;
}


}

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

在我看来,它并不适合 Hibernate,特别是从答案中比较长度、名称和某些实体的子实体的例子。Hibernate 建议在 equals ()和 hashCode ()中使用 使用商业密钥,他们有自己的理由。如果在业务键上使用 auto equals ()和 hashCode ()生成器,那么没有问题,只是需要像前面提到的那样考虑性能问题。但是人们通常使用的所有属性都是 IMO 很错误的。例如,我目前正在从事一个项目,其中的实体是使用 Pojomic 和@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 () a 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 (我从不使用组合键)。

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

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

各位,醒醒!从 Java 7开始标准库中有 等于HashCode的辅助方法。它们的用法完全等同于番石榴方法的用法。