重写持久实体的哈希码 () 和等于 () 方法的正确方法是什么?

2022-09-03 12:37:27

我有一个简单的类角色:

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

现在我想覆盖它的方法等于和哈希码。我的第一个建议是:

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

public int hashCode () {
    return id; 
}

但是当我创建新的角色对象时,它的 id 是 null。这就是为什么我在hashCode方法实现方面有一些问题。现在我可以简单地返回,但是如果角色名称不是必需字段怎么办?我几乎可以肯定,编造更复杂的例子并不难,这些例子无法通过返回其中一个字段的哈希代码来解决。roleName.hashCode ()

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


答案 1

Bauer 和 King 的《Java Persistence with Hibernate》一书建议不要将 key 字段用于 equals 和 hashCode。他们建议你应该挑选出对象的业务键字段(如果没有人工键),并使用这些字段来测试相等性。因此,在这种情况下,如果角色名称不是必需的字段,您将找到必需的字段并组合使用它们。在你发布的代码中,除了id之外,角色名称就是你所拥有的一切,角色名称将是我的首选。

以下是第398页的一段话:

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

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

我用来构造 equals 和 hashcode 方法的一种简单方法是创建一个返回“业务键”字段值的 toString 方法,然后在 equals() 和 hashCode() 方法中使用它。澄清:当我不关心性能时(例如,在rinky-dink内部Web应用程序中),这是一种懒惰的方法,如果性能预计会成为一个问题,那么请自己编写方法或使用IDE的代码生成工具。


答案 2

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

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

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

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

做法是使用相同的字段来生成与 equals() 方法使用的哈希码值相同的字段,并且顺序相同。以下是我对您的哈希码方法的替换:


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

注意:我不知道您使用id字段作为哈希码的意图,或者您打算如何处理id字段。我从注释中看到它是生成的,但它是外部生成的,因此编写的类无法履行契约。

如果由于某种原因,你发现自己处于这样一种情况,即这个类是由另一个类专门管理的,它忠实地为履行契约的 roleNames 生成“id”值,那么你就不会有功能问题,但这仍然是不好的做法,或者至少有人们所说的“代码气味”。除了类定义中没有任何内容可以保证类只能以这种方式使用之外,哈希码不是id,所以id不是哈希码

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

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

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


推荐