我们是否需要每次在 sub class 中生成 equals 和 hashcode,甚至在 java 中生成和序列化 super class?

Do we need to generate equals and hashcode in sub class everytime even super class generated and serialized in java?

我有一个class叫User.java,代码如下,

public abstract class User implements Serializable {

   // serialVersionUID

   private Integer userId;
   private String userName;
   private String fullName;

   // Constructor()
   // Getters and Setters
   // equals()
   // hashCode()
}

我有 Contact.java class,

public class Contact extends User {

   // serialVersionUID

   private String phoneNumber;
   private String address;

   // Constructor()
   // Getters and Setters
}

所以我的问题是即使用户 class 生成了 equalshashcode 方法,我是否需要在子 class 联系人中再次覆盖它?

而且我正在使用 lombok 而 IDE 是 IntelliJ。我看到当我通过 IDE 生成 equalshashcode 时,有 select 模板,例如,

生成时我看到生成的哈希码不同,例如,

那么它们之间的区别是什么,每个生成的 hascode() 之间的区别是什么?

我可以尝试解决这个问题吗?

通常情况下,是的,但首先您需要考虑在 User 对象的上下文中相等甚至意味着什么。例如,我敢打赌您会认为 ID 相等的任何用户都是相等的,并且没有必要或没有必要检查 userNamefullName 字段中的相等性,或者 phoneNumber 字段。可能您只想检查 userName,或者只想检查 userIduserName 的组合。这与您要编写的代码无关,而是与您认为任意两个给定用户对象之间的定义相等关系有关。

答案很复杂。

这个问题没有简单的答案。因此,让我们先谈谈为什么你甚至想要添加这样的方法。

什么是equals/hashCode

这两种方法的重点是让您能够使用 User class(或 Contact class)的实例作为键java.util.Map 某种形式,或者将它们放入某些 java.util.List 中,并让这些类型真正实现其文档告诉您的内容。如果您将没有 equals/hashCode impls 的类型的对象添加到 ArrayList,然后对此调用 .contains(),它不会按照您的预期进行。如果您尝试将它们用作地图中的键,它将无法正常工作。

因此,如果您不打算将 User 对象放入列表或映射中,则无需为这些东西烦恼。根本不写方法。继续生活。

好的,但我确实想这样做。

这让我们..

然后想想你的类型到底代表什么

联系人实例 class 实际上代表什么?

  • 它表示数据库中的一行(甚至可以是文本文件或基于 non-SQL 的引擎);例如,对对象的更改将导致对基础数据库的更改。
  • 代表用户通讯录中的联系人。不像 'the data store that the contact app I am writing is using' 那样 'address book',而是 'the actual address book'.

根据您的回答,equals/hashCode 会 不同。

代表数据库中的一行

对于SQL-based 数据库引擎,数据库引擎对等式有一个清晰且普遍理解的定义。如果您说 Contact 的一个实例代表您的数据库中的一行,那么您的 java 代码中的相等定义必须与 SQL 的相等定义相匹配是合乎逻辑的,并且该定义就是这样的:Equal Primary钥匙?那么他们就相等了。

PK 可以是任何东西,也可以是多列,但是绝大多数数据库设计使用 auto-generated 单个数字列作为主键,并且假设您有一个 'userID' 作为字段, 这听起来像你的设计。

这意味着您想要的平等定义是:平等意味着:相同的用户ID。但情况变得更糟:例如hibernate 你可以创建一个 User 的实例,并让该对象存在于 java 内存中,而无需将它保存到数据库中。这意味着(对于 auto-generated 主键),对象实际上 没有主键 并且对于没有主键的 2 个对象,即使它们在所有方式,这意味着它们 不相等 - 这是一个如此疯狂的相​​等定义(如果 userID 字段相等,则相等,除非 userID 字段表示占位符值指示 'not saved to db yet',则不相等,即使它们在各个方面都相同),没有 auto-generated 工具实现这一点,您必须自己编写。

代表通讯录中的一个条目

然后定义被有效地翻转:联系人条目有一个 id 是数据库的一个实现细节,并且是整个 class 中唯一不是联系人实际固有部分的字段,因为一个概念,因此平等可能最好定义为:“相同,除了 DB id,这无关紧要,因为它不是联系人的固有 属性”。

这里你需要再做一些额外的操作; lombok、intellij、eclipse——所有这些工具都不知道这些,并且默认情况下只会假设您打算仅当 every 字段相等时 2 个实例相等。

嗯..我该如何选择?

好吧,回到 equals/hashCode 的用途:让这些东西的实例在地图和 contains 中充当键,这样当这些实例存储在 java 列表。那么,如果您将 Contact 的 2 个单独实例存储到一个列表中,并且它们以某种方式具有相同的 ID,但用户名值不同,您希望发生什么?根据您的回答,您知道这两种解释中哪一种是正确的。

为什么这些工具会生成不同的 hashCode 实现?

他们没有。并不真地。 hashCode的要点很简单:

如果任何两个给定对象具有不同的哈希码,则它们不可能相等。

就是这样。这就是它的全部意思。具有相同哈希码的两个对象不需要相等(您必须调用 a.equals(b) 才能找出答案),但是 to 具有 non-equal 哈希码的对象不相等,无需调用 a.equals(b) 来查找。这根本不是 java 强制执行的,但你应该这样写(确保如果 2 个对象具有 non-equal 哈希码,则它们不能相等)。如果你不这样做,你的 classes 的实例在用作例如哈希图中的键。

有很多方法可以编写导致这种效果的算法。这解释了为什么会有细微差别。但是,它们都差不多有效(它们为已知的不同对象生成不同的哈希码的效率差不多,并且 hashCode 方法的运行性能差不多,对于所有这些不同的工具)。

子类型化

就相等性而言,子类型化极其复杂。这是由于 Object 的 equals 和 hashCode javadoc 中记录的规则。这个答案已经很长了,所以我不会解释为什么,所以你只需要进行一些网络搜索或相信我的话。然而,在面对类型层次结构时要求工具 auto-gen equality/hashCode impls 是非常棘手的。

听起来你确实想锁定 class User 级别的相等性(以及哈希码)的含义(即:相等性是通过具有相同的用户 ID 来定义的。如果听起来更像,也许是相同的用户名适用于你的情况。但仅此而已),在这种情况下你应该自己编写这些方法,它们应该是:

public final boolean equals(Object other) {
    if (other == this) return true;
    if (this.userId == null) return false;
    if (!(other instanceof User) return false;
    return this.userId.equals(((User) other).userId);
}

public final int hashCode() {
    return userId == null ? System.identityHashCode() : 61 * userId.intValue();
}

为什么?

  • 这些通过 'both have a userID, and they are equal'.
  • 定义相等性
  • 它们符合规则(例如:任何 2 个相同的对象将必然具有相同的哈希码)。
  • 它们很简单。
  • 它们是 'final',因为否则这会变得非常复杂,并且你不能允许子类型重新定义你的平等,这是行不通的,因为 a.equals(b) 必须匹配 b.equals(a).
  • 为什么61?它只是一个任意选择的质数。几乎任何数字都可以;在特殊情况下,任意素数的效率非常非常高。

其实我想要另一个定义

然后使用 lombok(免责声明:我是那里的核心贡献者,所以这是我自己的评价),因为它具有最好的 equals 实现,您不必查看代码或维护它。用 @EqualsAndHashCode.Exclude 注释标记你的 userId 字段,用 @EqualsAndHashCode(callSuper = true) 标记联系人 class,用 @EqualsAndHashCode 标记用户(或包含它的东西,比如 @Value ) - 需要 callSuper 来告诉 lombok 父级 class 有一个 lombok-compatible 等于实现。