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

我们是否每次都需要在子类中生成equals和hashcode,甚至是在java中生成和序列化的超类?,java,serialization,equals,hashcode,lombok,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()

我有一个名为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类

所以我的问题是,即使是用户类生成的equals和hashcode方法,我是否需要在子类Contact中再次重写它

我也在使用lombok,IDE是IntelliJ。我看到,当我通过IDE生成equals和hashcode时,需要选择模板,例如

违约 ApacheCommonsLang3就是这样。 生成时,我看到生成的hashcode是不同的,例如

lombok包含不同的代码 ApacheCommonsLang3包含不同的代码 那么,它们之间的区别是什么,生成的每个hascode之间的区别是什么


对不起,我对这件事缺乏了解。有人能帮我解决这个问题吗?提前感谢。

通常是的,但首先你需要考虑平等在用户对象的上下文中意味着什么。例如,我敢打赌,你会考虑任何一个ID相等的用户,并且在用户名或FulnNeX字段或PosiNoMe字段中不需要或检查是否相等。您可能只想检查用户名,或者只检查userId和userName的组合。它不是关于你想写的代码,而是关于你认为定义任何两个给定的用户对象之间的相等关系。 答案很复杂。 这个问题没有简单的答案。因此,让我们首先讨论一下为什么要添加这样的方法

equals/hashCode的作用是什么 这两种方法的要点是让您能够使用用户类或联系人类的实例作为某种java.util.Map中的键,或者将它们放在一些java.util.List中,让这些类型真正实现它们的文档告诉您的内容。如果将不带equals/hashCode impls的类型的对象添加到ArrayList中,然后在此基础上调用.contains,它将不会执行预期的操作。如果您尝试将它们用作地图中的关键点,则不会正常工作

因此,如果您不打算将用户对象放在列表或地图中,那么就没有必要担心这些东西。根本不要写方法。继续生活

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

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

它表示数据库中的一行,甚至可以是文本文件或非基于SQL的引擎;例如,对对象的更改将导致对底层数据库的更改。 它表示用户通讯簿中的联系人。不是“我正在编写的联系人应用程序正在使用的数据存储”中的“地址簿”,而是“实际的地址簿”。 根据您的答案,equals/hashCode将大不相同

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

PKs可以是任何东西,也可以是多列,但绝大多数DB设计都使用自动生成的单个数字列作为主键,并且假设您有一个“userID”作为字段,这听起来像您的设计

这意味着你想要的平等的定义是:平等意味着:相同的用户ID。但更糟糕的是:例如hibernate,您可以创建一个User实例,并将该对象存在于java内存中,而无需将其保存到DB中。这意味着对于自动生成的主键,对象实际上没有主键,对于没有主键的两个对象,即使它们在所有方面完全相同,因此,这意味着它们不相等-如果userID字段相等,这是一个疯狂的相等定义,除非userID字段表示表示“尚未保存到db”的占位符值,否则不相等,即使它们在各个方面都相同,也没有自动生成的工具实现这一点,你得自己写

它表示通讯簿中的一个条目 然后定义被有效地翻转:联系人条目具有id是数据库的一个实现细节,是整个类中作为概念不是联系人的实际固有部分的唯一字段,因此平等性最好定义为:相同,但DB id除外,这并不重要,因为这不是触点的固有属性

同样,你需要在这里采取一些额外的行动;lombok、intellij、eclipse—所有这些工具都不知道这一点,默认情况下只会假设只有当每个字段相等时,两个实例才相等

嗯。。我如何选择? 那么,回到什么平等 s/hashCode的作用是:使这些东西的实例作为映射中的键和contains中的键,并在这些实例存储在java列表中时给出正确的答案。那么,如果您将联系人的两个独立实例存储到一个列表中,其中它们都具有相同的ID,但用户名值不同,您希望发生什么?根据你的回答,你知道这两种解释中哪一种是正确的

为什么这些工具会生成不同的哈希代码? 他们没有。不是真的。hashCode的要点非常简单:

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

就这样。这就是它的全部意思。具有相等哈希代码的两个对象不需要相等您必须调用a.equalsb才能找到,但具有不相等哈希代码的两个对象不相等,并且不需要调用a.equalsb才能找到。java根本没有强制执行这一点,但您应该以这种方式编写它,以确保如果两个对象的哈希代码不相等,那么它们就不能相等。如果您没有做到这一点,那么类的实例在用作hashmaps中的键时会做一些奇怪的事情

有很多方法可以编写一个算法来产生这种效果。这就解释了为什么会有微小的差异。但是,它们都是同样有效的,它们为已知的不同对象生成不同的hashCode,效率也一样,hashCode方法对于所有这些不同的工具运行的性能也差不多相同

亚型 就平等而言,分型极其复杂。这是由于对象的equals和hashCode javadoc中记录的规则造成的。这个答案已经很长了,所以我不会解释为什么,所以你只需要做一些网络搜索或者相信我的话。然而,要求工具在类型层次结构面前自动生成相等/哈希代码impls是非常棘手的

这听起来确实像是要锁定相等性,因此hashcode在类用户级别的含义是:相等性是通过具有相同的userID来定义的。如果听起来更适合你的情况,也许是相同的用户名。除此之外,在这种情况下,您应该自己编写这些方法,它们应该是:

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();
}
为什么?

它们通过“两者都有一个用户ID,并且它们是相等的”来定义相等性。 它们符合如下规则:任意两个相等的对象都必须具有相等的哈希代码。 它们很简单。 它们是“最终的”,因为否则这会变得非常复杂,你不能允许子类型在你身上重新定义相等,这是行不通的,因为a.equalsb必须与b.equalsa匹配。 为什么是61岁?它只是一个任意选择的素数。几乎任何数字都可以;在奇异情况下,任意素数的效率要稍微高一点。 实际上我想要另一个定义
然后使用lombok免责声明:我是那里的核心贡献者,所以这是我自己的评级,因为它有最好的equals实现,您不必查看代码或维护它。用@EqualsAndHashCode.Exclude注释标记您的userId字段,用@equalsandhascodecallsuper=true标记Contact类,用@EqualsAndHashCode或包含该值的内容标记用户,如@Value-需要调用super来告诉lombok父类具有与lombok兼容的equals实现。

通常,是,但首先,您需要考虑平等在用户对象上下文中的含义。例如,我敢打赌,你会考虑任何一个ID相等的用户,并且在用户名或FulnNeX字段或PosiNoMe字段中不需要或检查是否相等。您可能只想检查用户名,或者只检查userId和userName的组合。它不是关于你想写的代码,而是关于你认为定义任何两个给定的用户对象之间的相等关系。 答案很复杂。 这个问题没有简单的答案。因此,让我们首先讨论一下为什么要添加这样的方法

equals/hashCode的作用是什么 这两种方法的要点是让您能够使用用户类或联系人类的实例作为某种java.util.Map中的键,或者将它们放在一些java.util.List中,让这些类型真正实现它们的文档告诉您的内容。如果将不带equals/hashCode impls的类型的对象添加到ArrayList中,然后在此基础上调用.contains,它将不会执行预期的操作。如果您尝试将它们用作地图中的关键点,则不会正常工作

因此,如果您不打算将用户对象放在列表或地图中,那么就没有必要担心这些东西。根本不要写方法。继续生活

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

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

它表示数据库中的一行,甚至可以是文本文件或非基于SQL的行 发动机例如,对对象的更改将导致对底层数据库的更改。 它表示用户通讯簿中的联系人。不是“我正在编写的联系人应用程序正在使用的数据存储”中的“地址簿”,而是“实际的地址簿”。 根据您的答案,equals/hashCode将大不相同

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

PKs可以是任何东西,也可以是多列,但绝大多数DB设计都使用自动生成的单个数字列作为主键,并且假设您有一个“userID”作为字段,这听起来像您的设计

这意味着你想要的平等的定义是:平等意味着:相同的用户ID。但更糟糕的是:例如hibernate,您可以创建一个User实例,并将该对象存在于java内存中,而无需将其保存到DB中。这意味着对于自动生成的主键,对象实际上没有主键,对于没有主键的两个对象,即使它们在所有方面完全相同,因此,这意味着它们不相等-如果userID字段相等,这是一个疯狂的相等定义,除非userID字段表示表示“尚未保存到db”的占位符值,否则不相等,即使它们在各个方面都相同,也没有自动生成的工具实现这一点,你得自己写

它表示通讯簿中的一个条目 然后定义被有效地翻转:联系人条目具有id是数据库的一个实现细节,是整个类中作为概念不是联系人的实际固有部分的唯一字段,因此平等性最好定义为:相同,但DB id除外,这并不重要,因为这不是触点的固有属性

同样,你需要在这里采取一些额外的行动;lombok、intellij、eclipse—所有这些工具都不知道这一点,默认情况下只会假设只有当每个字段相等时,两个实例才相等

嗯。。我如何选择? 好吧,回到equals/hashCode的用途:使这些东西的实例作为映射中的键和contains中的键来运行,当这些实例存储在java列表中时,给出正确的答案。那么,如果您将联系人的两个独立实例存储到一个列表中,其中它们都具有相同的ID,但用户名值不同,您希望发生什么?根据你的回答,你知道这两种解释中哪一种是正确的

为什么这些工具会生成不同的哈希代码? 他们没有。不是真的。hashCode的要点非常简单:

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

就这样。这就是它的全部意思。具有相等哈希代码的两个对象不需要相等您必须调用a.equalsb才能找到,但具有不相等哈希代码的两个对象不相等,并且不需要调用a.equalsb才能找到。java根本没有强制执行这一点,但您应该以这种方式编写它,以确保如果两个对象的哈希代码不相等,那么它们就不能相等。如果您没有做到这一点,那么类的实例在用作hashmaps中的键时会做一些奇怪的事情

有很多方法可以编写一个算法来产生这种效果。这就解释了为什么会有微小的差异。但是,它们都是同样有效的,它们为已知的不同对象生成不同的hashCode,效率也一样,hashCode方法对于所有这些不同的工具运行的性能也差不多相同

亚型 就平等而言,分型极其复杂。这是由于对象的equals和hashCode javadoc中记录的规则造成的。这个答案已经很长了,所以我不会解释为什么,所以你只需要做一些网络搜索或者相信我的话。然而,要求工具在类型层次结构面前自动生成相等/哈希代码impls是非常棘手的

这听起来确实像是要锁定相等性,因此hashcode在类用户级别的含义是:相等性是通过具有相同的userID来定义的。如果听起来更适合你的情况,也许是相同的用户名。除此之外,在这种情况下,您应该自己编写这些方法,它们应该是:

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();
}
为什么?

它们通过“两者都有一个用户ID,并且它们是相等的”来定义相等性。 它们符合如下规则:任意两个相等的对象都必须具有相等的哈希代码。 它们很简单。 它们是“最终的”,因为否则这会变得非常复杂,你不能允许子类型在你身上重新定义平等,因为 a.equalsb必须与b.equalsa匹配的规则。 为什么是61岁?它只是一个任意选择的素数。几乎任何数字都可以;在奇异情况下,任意素数的效率要稍微高一点。 实际上我想要另一个定义
然后使用lombok免责声明:我是那里的核心贡献者,所以这是我自己的评级,因为它有最好的equals实现,您不必查看代码或维护它。用@EqualsAndHashCode.Exclude注释标记您的userId字段,用@EqualsAndHashCodecallSuper=true标记Contact类,用@EqualsAndHashCode或包含该注释的内容标记User,与@Value一样,需要调用超级函数来告诉lombok父类有一个与lombok兼容的equals实现。

抽象类通常不应该重写equals和hashCode,除非将它们抽象化以强制具体类这样做。这完全取决于您想要如何比较它们。你想不想把电话号码和地址包括在里面?@chrylis小心地选择-java.util.AbstractList的开发人员会不同意你的意见。@DawoodibnKareem我说这话总的来说是有原因的。在AbstractList的例子中,它实现了一个接口,API中详细指定了这些方法的契约,特别是,不允许根据子类拥有的任何新状态进行更改。抽象类通常不应重写equals和hashCode,除非将其抽象以强制具体类这样做。这完全取决于您希望如何比较它们。你想不想把电话号码和地址包括在里面?@chrylis小心地选择-java.util.AbstractList的开发人员会不同意你的意见。@DawoodibnKareem我说这话总的来说是有原因的。在AbstractList的例子中,它实现了一个接口,API中详细规定了这些方法的契约,特别是不允许根据子类拥有的任何新状态而改变。现在我非常清楚这一点。非常感谢您的回答,非常感谢您尽全力回答我的问题。非常感谢。现在我对此非常清楚。非常感谢您的回答,非常感谢您尽全力回答我的问题。非常感谢你。