Apache Commons equals / hashCode builder

我很想知道,这里的人们在考虑使用org.apache.commons.lang.builder EqualsBuilder / HashCodeBuilder来实现equals / hashCode ? 这会比写自己的做法更好吗? 它与Hibernate一起玩吗? 你怎么看?

commons / lang的build设者是伟大的,我一直在使用他们多年,没有明显的性能开销(有和没有hibernate)。 但正如阿兰所写,番石榴的方式更好:

这里是一个示例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; } } 

在这里与番石榴:

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

正如你所看到的番石榴版本较短,避免了多余的辅助对象。 在等号的情况下,如果早期的Object.equal()调用返回false(公平:commons / lang的ObjectUtils.equals(obj1, obj2)方法具有相同的语义,则甚至可以使评估短路如上使用而不是EqualsBuilder来允许短路)。

所以:是的,普通的lang构build器比手动构build的equals()hashCode()方法(或Eclipse会为您生成的那些可怕的怪物equals()更可取,但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()合同的解释,您可能还会更改行(s)

  if(obj instanceof Bean){ 

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

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

正确的方式将超类组合到一个Guava Objects.hashcode()实现?

(尽pipe它是关于hashCode()equals()


注意(由kayahr的评论启发)

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

伙计,醒来! 由于Java 7在标准库中有equals和hashCode的辅助方法。 它们的用法完全等同于使用番石榴的方法。

如果你不想依靠第三方库(也许你正在运行一个资源有限的设备),你甚至不想input你自己的方法,那么你也可以让IDE做这个工作,比如在eclipse中使用

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

您将获得“本地”代码,您可以根据自己的喜好进行configuration,并且必须支持更改。


例子(日食朱诺):

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

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

  • 空处理
  • 实例创build

EqualsBuilder和HashCodeBuilder使比较可能为空的字段更容易。 通过手动编写代码,会创build大量的样板文件。

另一方面,EqualsBuilder会根据等号方法调用创build一个实例。 如果你的equals方法经常调用,将会创build很多实例。

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

正如skaffman所说的reflection版本不能用于生产代码。 反思会慢下来,对于除了最简单的阶级以外的所有阶层,“执行”都是不正确的。 考虑到所有成员也是危险的,因为新引入的成员改变平等方法的行为。 reflection版本在testing代码中可能是有用的。

如果你不写自己的,也有可能使用谷歌番石榴(以前的谷歌collections)

如果您只是处理其中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,特别是从答案比较长度,名称和孩子的一些实体的例子。 Hibernatebuild议使用业务键在equals()和hashCode()中使用,他们有他们的理由。 如果您在业务密钥上使用自动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是相等的)。 但是当我们在所有道具上实现自动equals()hashCode()时,我们有什么?

  1. 当你将entity2放到entity1已经存在的持久集中时,这会被放置两次,并在提交时导致exception。
  2. 如果你想把分离的entity2附加到会话,其中entity1已经存在,他们(可能,我没有特别testing过)将不会被合并。

因此,对于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(我从来没有使用复合键)。

为了以防万一,其他人会觉得它有用,我已经拿出这个助手类的哈希码计算,避免额外的对象创build开销上面提到(事实上,Object.hash()方法的开销,当你有inheritance,因为它将在每个级别上创build一个新的数组!)。

用法示例:

 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是一个领域模型中属性的最大合理数量,如果你有更多的你应该考虑重构和引入更多的类,而不是维护一堆string和原语。

缺点是:如果你主要有原始数据和/或数组,你就需要深入哈希,这是没有用的。 (通常情况下,当你不得不处理不受控制的平面(转移)对象时就是这种情况)。