我是否应该在子类中重写equals和hashCode,即使它没有添加任何东西?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我是否应该在子类中重写equals和hashCode,即使它没有添加任何东西?相关的知识,希望对你有一定的参考价值。
我有抽象类来覆盖equals()
和hashCode()
。在那些方法中,我使用抽象方法getDescription()
来检查相等性并生成hashCode。现在当我扩展类并添加一个仅在getDescription()
方法中使用的字段时,我得到一个sonarLint问题“扩展一个覆盖等于并添加字段的类”。仅仅是Sonar不够精明,无法理解发生了什么,或者我是不是以java方式做到这一点而且有更好/更优雅的方式?
家长班:
public abstract String getDescription();
@Override
public int hashCode()
{
return new HashCodeBuilder(19, 71).
append(mViolation).
append(getDescription()).
append(mProperties).
toHashCode();
}
@Override
public boolean equals(
final Object obj)
{
boolean equal = false;
if (this == obj)
{
equal = true;
}
else if (obj instanceof parent)
{
AbstractStructuredDataIssue rhs = (parent) obj;
equal = new EqualsBuilder().
append(mViolation, rhs.mViolation).
append(getDescription(), rhs.getDescription()).
append(mProperties, rhs.mProperties).
isEquals();
}
return equal;
}
儿童班:
public class Child extends Parent {
private final String mMessage;
public Child(final String message, final int number) {
super(number);
mMessage = message;
}
@Override
public String getDescription()
{
return String.format(
DESCRIPTION_FORMAT,
mMessage);
}
}
这有点复杂;我必须解释一些关于equals和hashCode如何解释可行解决方案的事情。
有一个'合同'。编译器无法强制执行,但如果您不遵守此合同,则会发生奇怪的事情。具体来说:当用作哈希映射中的键时,您的对象只会做错事,并且在使用第三方库时可能会出现其他类似问题。为了正确遵守契约,任何给定的类都需要完全选择退出equals / hashCode,OR,整个链(所以,类及其所有子类)需要正确覆盖hashCode和equals,除此之外,你真的可以'除非父母有适当的仪器,否则不要这样做。
合同规定这必须始终正确:
- a.equals(b) - > b.equals(a)。
- a.equals(b)和b.equals(c) - > a.equals(c)。
- a.equals(a)中。
- a.equals(b) - > a.hashCode()== b.hashCode()。 (注意,反之不一定是真的;相等的哈希码并不意味着对象是相等的)。
面对阶级层级,合同真的很难保证!想象一下,我们采用现有的java.util.ArrayList并将其子类化为'color'的概念。所以现在我们可以有一个蓝色的ColoredArrayList,或一个红色的ColoredArrayList。说明蓝色ColoredArrayList绝对不应该等于红色ColoredArrayList,除了...等于ArrayList本身的impl(你无法改变),这是非常有意义的,有效地定义你根本无法使用这样的属性扩展ArrayList:如果你调用a.equals(b)其中a是一个空的arraylist而b是一个空的List(比如一个空的红色ColoredArrayList),它只会检查其中每个成员的相等性,因为它们都是空的,很简单。因此,空正常arraylist等于空红色和空蓝色ColoredArrayList,因此合同规定空红色ColoredArrayList必须等于空蓝色ColoredArrayList。从这个意义上讲,声纳只是在这里打破了。有一个问题,它是不可修复的。在java中编写ColoredArrayList的概念是不可能的。
然而,有一个解决方案,但只有当层次结构中的每个类都在船上时。这是canEqual
方法。如上所述,摆脱色彩困境的方法是区分“我正在扩展,添加新属性”和“我正在扩展”这一概念,但是,这些东西在语义上仍然完全相同,没有新属性。 ColoredArrayList是前一种情况:它是一个添加新属性的扩展。 canEqual的想法是你创建一个单独的方法来指示这个,这让ArrayList弄清楚:我不能等于任何ColoredArrayList实例,即使所有元素都是相同的。然后我们可以再次遵守合同。 ArrayList没有这个系统,因此,如果你不能更改ArrayList的源代码,你就会陷入困境:它无法修复。但是,如果您编写自己的类层次结构,则可以添加它。
Project Lombok负责为您添加equals和hashCode。即使您不想使用它,也可以查看它生成的内容并在您自己的代码中复制它。这也将删除声纳发出的警告。请参阅https://projectlombok.org/features/EqualsAndHashCode - 这也向您展示了如何使用canEqual
概念来避免ColoredArrayList困境。
这里你是子类而不添加新属性,因此,实际上不需要替换hashCode和equals。但声纳不知道这一点。
让我们来看看规则RSPEC-2160:
扩展一个覆盖等于的类并添加字段而不覆盖子类中的equals,并且您冒着子类的非等效实例被视为相等的风险,因为在相等测试中只考虑超类字段。
Sonar指出的是你将不相等的对象视为相等的风险,因为当你在你的子类中调用equals
时,没有正确的实现,只会评估父类字段。
不合规的代码示例(来自文档)
public class Fruit {
private Season ripe;
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (this.class != obj.class) {
return false;
}
Fruit fobj = (Fruit) obj;
if (ripe.equals(fobj.getRipe()) {
return true;
}
return false;
}
}
public class Raspberry extends Fruit { // Noncompliant; instances will use Fruit's equals method
private Color ripeColor;
}
合规解决方案(也来自文档)
public class Fruit {
private Season ripe;
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (this.class != obj.class) {
return false;
}
Fruit fobj = (Fruit) obj;
if (ripe.equals(fobj.getRipe()) {
return true;
}
return false;
}
}
public class Raspberry extends Fruit {
private Color ripeColor;
public boolean equals(Object obj) {
if (! super.equals(obj)) {
return false;
}
Raspberry fobj = (Raspberry) obj;
if (ripeColor.equals(fobj.getRipeColor()) { // added fields are tested
return true;
}
return false;
}
}
我同意你的观点,即Sonar可能不够复杂,无法看到运行时发生的事情,因此它指出了Code Smell。
你需要担心不打破equals
和hashCode
合同,你的方法是动态的,这可能不是Sonar所考虑的。
- 考虑子类成员(变量),覆盖子类中的equals()和hashcode()方法将是有效的,并且它还有助于在集合框架操作期间使用Collection框架和Map实例的子类型来查找正确的内存空间(存储区)(例如:保存/检索)。 在这里继承超类可能会错过子类成员来有效地生成hashcode / equals方法功能。
使用你的实现,你可以有两个相同的Parent
引用,但指向两个不同类的对象,这样一个可以转换为Child
而另一个 - 不是。
这是非常意外的,可能会导致问题 - 而Sonar的工作就是指出它。如果你认为你的用例是合理的,那就用Sonar的警告记录下来(这就是我要做的)。
以上是关于我是否应该在子类中重写equals和hashCode,即使它没有添加任何东西?的主要内容,如果未能解决你的问题,请参考以下文章