每次在java中生成和序列化超类时,我们是否都需要在子类中生成equals和hashcode?

H A*_*ala 1 java serialization equals hashcode lombok

我有一个名为的类User.java,代码如下,

public abstract class User implements Serializable {

   // serialVersionUID

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

   // Constructor()
   // Getters and Setters
   // equals()
   // hashCode()
}
Run Code Online (Sandbox Code Playgroud)

而且我还有Contact.java课,

public class Contact extends User {

   // serialVersionUID

   private String phoneNumber;
   private String address;

   // Constructor()
   // Getters and Setters
}
Run Code Online (Sandbox Code Playgroud)

所以我的问题是,即使生成了 User 类,equalshashcode是否需要在子类 Contact 中再次重写它?

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

  • 默认
  • Apache commons lang 3 就是这样。

生成时我发现生成的哈希码是不同的,例如,

  • lombok包含不同的代码
  • Apache commons lang 3包含不同的代码

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

我可以尝试什么来解决这个问题?

rzw*_*oot 6

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

答案很复杂。

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

equals/hashCode 的作用是什么

这两种方法的目的是使您能够使用User类(或您的Contact类)的实例作为java.util.Map某种类型的键,或者将它们放入某些类型中java.util.List并使这些类型真正满足其文档告诉您的内容。如果您将没有 equals/hashCode impls 的类型的对象添加到 an 中,ArrayList然后调用.contains()它,它不会执行您期望的操作。如果您尝试将它们用作地图中的键,则它将无法正常工作。

因此,如果您不打算将 User 对象放入列表或地图中,则无需担心这些事情。根本不写方法。继续生活。

好吧,但我确实想这么做。

这让我们..

然后想想你的类型实际上代表什么

Contact 类的实例实际上代表什么?

  • 它代表数据库中的一行(甚至可以是文本文件或非基于 SQL 的引擎);例如,对象的更改将导致底层数据库的更改。
  • 它代表用户地址簿中的联系人。不是“我正在编写的联系人应用程序正在使用的数据存储”中的“地址簿”,而是“实际的地址簿”。

根据您的答案, equals/hashCode 将会有很大不同。

它代表数据库中的一行

对于基于 SQL 的数据库引擎,数据库引擎对相等性有一个清晰且普遍理解的定义。如果您说 Contact 的实例代表数据库中的一行,那么您的 java 代码中的相等定义必须与 SQL 的相等定义相匹配是合乎逻辑的,并且该定义很简单:相等主键?那么他们是平等的。

主键可以是任何内容,也可以是多列,但绝大多数数据库设计都使用自动生成的单个数字列作为主键,并且考虑到您有一个“userID”作为字段,这听起来像您的设计。

这意味着您想要的平等的定义是:平等意味着:相同的 userID。但情况会变得更糟:例如,在 hibernate 中,您可以创建 User 的实例并将该对象存在于 java 内存中,而无需将其保存到数据库中。这意味着(对于自动生成的主键),该对象实际上没有主键,而对于没有主键的 2 个对象,即使它们在所有方面都完全相同,这意味着它们不相等- 并且这是一个疯狂的相等定义(如果 userID 字段相等,则相等,除非 userID 字段表示指示“尚未保存到数据库”的占位符值,则不相等,即使它们在各个方面都相同),没有 auto - 生成的工具实现了这个,你必须自己编写它。

它代表地址簿中的一个条目

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

同样,您需要在这里采取一些额外的操作;lombok、intellij、eclipse——所有这些工具都无法知道这些,并且默认情况下只会假设您打算仅在每个字段都相等时才使 2 个实例相等。

嗯..我该如何选择?

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

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

他们不这样做。并不真地。hashCode 的要点很简单:

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

就是这样。这就是全部意思。具有相等哈希码的两个对象不需要相等(您必须调用a.equals(b)才能找出),但是具有不相等哈希码的两个对象不相等,并且不需要调用来a.equals(b)找出答案。这根本不是由java强制执行的,但是你应该这样写(确保如果两个对象有不相等的哈希码,它们不能相等)。如果你不这样做,你的类的实例在用作哈希图中的键时将会做出奇怪的事情。

有很多方法可以编写实现这种效果的算法。这解释了为什么存在微小差异。但是,它们都同样有效(它们为已知的不同对象生成不同的哈希码的效率大约相同,并且对于所有这些不同的工具,hashCode 方法的运行性能大约相同)。

子类型化

就平等而言,子类型极其复杂。这是由于对象的 equals 和 hashCode javadoc 中记录的规则所致。这个答案已经很长了,所以我不会解释为什么,所以你只需要进行一些网络搜索或相信我的话。然而,要求工具在类型层次结构中自动生成相等/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();
}
Run Code Online (Sandbox Code Playgroud)

为什么?

  • 它们通过“两者都有用户 ID,并且它们是相等的”来定义相等性。
  • 它们符合规则(例如:任何两个相等的对象必然具有相等的哈希码)。
  • 它们很简单。
  • 它们是“最终的”,因为否则这会变得非常复杂,并且您不能允许子类型重新定义您的相等性,这是行不通的,因为必须匹配的a.equals(b)规则b.equals(a)
  • 为什么61?它只是一个任意选择的素数。几乎任何数字都可以;在特殊情况下,任意素数的效率非常非常高。

其实我想要另一个定义

然后使用lombok(免责声明:我是那里的核心贡献者,所以这是我对自己的评价),因为它具有最好的 equals 实现,并且您不必查看代码或维护它。使用@EqualsAndHashCode.Exclude注释标记您的 userId 字段,并使用 标记 Contact 类@EqualsAndHashCode(callSuper = true),使用@EqualsAndHashCode(或包含该内容的内容,例如@Value)标记 User - 需要 callSuper 来告诉 lombok 父类具有与 lombok 兼容的 equals 实现。