重写持久化实体的hashCode()和equals()方法的正确方法是什么?

我有一个简单的类角色:

@Entity @Table (name = "ROLE") public class Role implements Serializable { @Id @GeneratedValue private Integer id; @Column private String roleName; public Role () { } public Role (String roleName) { this.roleName = roleName; } public void setId (Integer id) { this.id = id; } public Integer getId () { return id; } public void setRoleName (String roleName) { this.roleName = roleName; } public String getRoleName () { return roleName; } } 

现在我想覆盖它的方法equals和hashCode。 我的第一个建议是:

 public boolean equals (Object obj) { if (obj instanceof Role) { return ((Role)obj).getRoleName ().equals (roleName); } return false; } public int hashCode () { return id; } 

但是当我创建新的Role对象时,它的id为null。 这就是我对hashCode方法实现有一些问题的原因。 现在我可以简单地返回roleName.hashCode ()但是如果roleName不是必需的字段呢? 我几乎可以肯定,通过返回其中一个字段的hashCode来构建更复杂的例子并不困难。

因此,我希望看到一些相关讨论的链接或听取您解决此问题的经验。 谢谢!

Bauer和King的书籍Java Persistence with Hibernate建议不要使用equals和hashCode的关键字段。 他们建议你应该选择对象的业务关键字段(如果没有人工密钥)并使用它们来测试相等性。 因此,在这种情况下,如果角色名称不是必需字段,您将找到必要的字段并将它们组合使用。 在你发布的代码的情况下,除了id之外你还拥有rolename,rolename就是我的目标。

这是第398页的引用:

我们认为基本上每个实体类都应该有一些业务键,即使它包含了类的所有属性(这适用​​于某些不可变类)。 业务键是用户的唯一标识特定记录的东西,而代理键是应用程序和数据库使用的。

业务键等式意味着equals()方法仅比较形成业务键的属性。 这是一个完美的解决方案,可以避免前面提到的所有问题。 唯一的缺点是,首先需要额外考虑识别正确的业务密钥。 无论如何都需要这种努力; 如果数据库必须通过约束检查确保数据完整性,则识别任何唯一键很重要。

我用来构造equals和hashcode方法的一种简单方法是创建一个toString方法,该方法返回’business key’字段的值,然后在equals()和hashCode()方法中使用它。 澄清:这是一种懒惰的方法,当我不关心性能时(例如,在rinky-dink内部webapps中),如果预期性能是一个问题,那么自己编写方法或使用IDE的代码生成工具。

我很抱歉因为批评而迟到,但没有其他人提到它, 这里有一个严重的缺陷 。 实际上可能是两个。

首先,其他人已经提到了如何处理null的可能性,但是一个好的hashcode()equals()方法对的一个关键元素是它们必须服从契约,而你上面的代码不会这样做。

合同是equals()返回true的对象必须返回相等的哈希码值 ,但在上面的类中,字段id和roleName是独立的。

这是一个致命缺陷的做法:您可以轻松地拥有两个具有相同roleName值但具有不同id值的对象。

实践是使用相同的字段来生成equals()方法使用的哈希码值,并且顺序相同。 下面是我的hashcode方法的替代品:

 public int hashCode () { return ((roleName==null) ? 0 : roleName.hashcode()); } 

注意:我不知道您使用id字段作为哈希码的意图,或者您对id字段的意图。 我从注释中看到它是生成的,但是它是外部生成的,因此所写的类无法履行合同。

如果由于某种原因你发现自己处于这样一种情况,即这个类完全由另一个类管理,忠实地为履行合同的roleNames生成“id”值,你就不会遇到function问题,但它仍然是不好的做法,或至少让人们称之为“代码味”。 除了类定义中没有任何东西可以保证类只能以这种方式使用之外, hashcode不是id,因此id不是hashcode

这并不意味着您不能使用保证等于 – 等于 – 角色名 – 值的标识符作为哈希码,但它们在概念上并不相同,所以至少,您应该有一个注释块解释你偏离预期的做法。

作为一个很好的一般规则,如果你发现自己必须这样做,你可能会犯一个设计错误。 不总是,但可能。 一个原因是什么? 人们并不总是阅读评论,所以即使你创建一个function完善的系统,随着时间的推移,有人会“滥用”你的课程并导致问题。

让类本身管理哈希码值的生成避免了这种情况。 并且您仍然可以保存并提供外部生成的ID,无论您使用它的目的是什么。

对象的业务键可能需要其父(或另一个一对一或多对一)关系。 在这些情况下,调用equals()或hashcode()可能会导致数据库命中。 除了性能之外,如果会话关闭将导致错误。 我大多放弃尝试使用业务键; 我使用主要ID并避免在地图和集合中使用未保存的实体。 到目前为止一直运作良好,但它可能取决于应用程序(注意通过父级联保存多个孩子)。 偶尔,我将使用一个单独的无意义的键字段,它是在构造函数中或由对象创建者自动生成的uuid。

知道何时覆盖hashCode和equals不是一件容易的事,还有另一个讨论,你在这里有示例和文档链接在Java中覆盖equals和hashCode时应该考虑哪些问题?

如前所述,您必须使用业务键来实现equal和hashCode。 此外,您必须使您的equals和hashCode实现为null安全或添加非null约束 (以及对代码的不变检查)以确保业务键永远不为null。

我想添加约束是解决问题的正确方法。 否则,将允许没有名称的角色实例,并且所有这些物理实例都将被视为相等。

请在下面找到如何使用Apache commons构建器创建hashCode,equals和toString方法的简单说明。

的hashCode

  1. 如果两个对象根据equals()方法相等,则它们必须具有相同的hashCode()值
  2. 两个不同的对象可能具有相同的hashCode()。
  3. 请使用唯一的Business ID进行hashCode创建(这意味着您应该使用一些代表业务实体的唯一属性,例如名称)
  4. Hibernate实体:请不要使用Hibernate id来创建hashCode
  5. 如果您的类是子类,您可以调用.appendSuper(super.hashCode())

     @覆盖
     public int hashCode(){
        返回新的HashCodeBuilder()
                 .append(的getName())
                 .toHashCode();
     }
    

等于

  1. 请比较商业ID(这意味着您应该使用代表商业实体的一些独特属性,例如,名称)
  2. Hibernate实体:请不要比较Hibernate id
  3. Hibernate Entity:在访问其他对象字段时使用getter让Hibernate加载属性
  4. 如果您的类是子类,您可以调用.appendSuper(super.equals(other))

     @覆盖
     public boolean equals(final Object other){
         if(this == other)
            返回true;
         if(!(TreeNode的其他实例))
            返回虚假;
         TreeNode castOther =(TreeNode)其他;
        返回新的EqualsBuilder()
                 .append(getName(),castOther.getName())
                 .isEquals();
     }
    

的toString

  1. 请确保toString不会抛出NullPointerException。
  2. 如果您的类是子类,您可以调用.appendSuper(super.toString())

     @覆盖
     public String toString(){
        返回新的ToStringBuilder(this)
                 .append(“Name”,getName())
                的ToString();
     }