关闭。这个问题是基于意见的。它目前不接受答案。想改进这个问题?更新问题,以便可以通过编辑这篇文章用事实和引用来回答它。 5年前关闭。改进这个问题
我很想知道,这里的人们对使用 org.apache.commons.lang.builder
EqualsBuilder
/HashCodeBuilder
来实现 equals
/hashCode
有何看法?这会比自己编写更好吗?它与 Hibernate 配合得好吗?你怎么看?
reflectionEquals
和 reflectionHashcode
函数所诱惑;表演是绝对的杀手锏。
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,但正如评论所指出的,该功能已经在 JDK 中引入,因此不再需要 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() 实现中使用惰性集合时要小心。如果您没有打开的会话,那将惨遭失败。
注意(关于 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()
)
注意(灵感来自 kayahr 的评论)
如果您有许多原始字段,Objects.hashCode(..)
(就像底层的 Arrays.hashCode(...)
)可能会表现不佳。在这种情况下,EqualsBuilder
实际上可能是更好的解决方案。
各位,醒醒吧! 从 Java 7 开始,标准库中有 equals 和 hashCode 的辅助方法。它们的用法完全等同于 Guava 方法的用法。
如果您不想依赖 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;
}
}
equals
这样可怕的事情。如果您不想依赖 3rd 方库,请自己编写像 Objects.equal
这样的单行方法。即使只使用一次或两次,它也会使代码变得更好!
equals
/hashCode
单行方法???
EqualsBuilder 和 HashCodeBuilder 有两个主要方面不同于手动编写的代码:
空值处理
实例创建
EqualsBuilder 和 HashCodeBuilder 可以更轻松地比较可能为空的字段。使用手动编写的代码,这会创建很多样板。
另一方面,EqualsBuilder 将为每个 equals 方法调用创建一个实例。如果经常调用您的 equals 方法,这将创建很多实例。
对于 Hibernate,equals 和 hashCode 实现没有区别。它们只是一个实现细节。对于几乎所有使用 hibernate 加载的域对象,可以忽略 Builder 的运行时开销(即使没有逃逸分析)。数据库和通信开销将很大。
正如 skaffman 提到的,反射版本不能在生产代码中使用。反射会变慢,并且除了最简单的类之外,“实现”不会对所有类都是正确的。考虑所有成员也是危险的,因为新引入的成员会改变 equals 方法的行为。反射版本在测试代码中很有用。
如果您只是处理以 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() 中使用 to use business key,他们有他们的理由。如果您在业务密钥上使用 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 和类/表是相等的)。但是当我们在所有道具上实现 auto equals() 和 hashCode() 时,我们有什么?
当您将 entity2 放入 entity1 已经存在的持久集中时,这将被放置两次,并在提交期间导致异常。如果您想将分离的 entity2 附加到 session,其中 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(我从不使用复合键)。
以防万一,其他人会发现它有用,我想出了这个用于哈希码计算的 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 是域模型中属性的最大合理数量,如果你有更多,你应该考虑重构和引入更多类,而不是维护一堆字符串和原语。
缺点是:如果您主要有需要深度散列的基元和/或数组,则它没有用。 (通常情况下,当您必须处理您无法控制的平面(转移)对象时)。
equals
也有同样的问题。 Guava 将所有值转换为对象,commons-lang 只创建一个新对象。