Java 我是否应该重写子类中的equals和hashCode,即使它';他没有添加任何内容吗?
我有一个抽象类,它重写了Java 我是否应该重写子类中的equals和hashCode,即使它';他没有添加任何内容吗?,java,sonarqube,sonarlint,Java,Sonarqube,Sonarlint,我有一个抽象类,它重写了equals()和hashCode()。在这些方法中,我使用抽象方法getDescription()检查相等性并生成hashCode。现在,当我扩展该类并添加一个仅在getDescription()方法中使用的字段时,我得到了一个sonarLint问题“扩展一个覆盖等于的类并添加字段”。是因为Sonar不够复杂,无法理解正在发生的事情,还是我用的不是java方式,而是有更好/更优雅的方式 父类: public abstract String getDescription(
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如何工作的事情来解释可行的解决方案 有一个“合同”。编译器不能强制执行它,但是如果你不遵守这个契约,奇怪的事情就会发生。具体地说:当在hashmaps中用作键时,您的对象只会做错误的事情,并且在使用第三方库时可能会出现其他类似的问题。为了正确地遵守契约,任何给定的类要么需要完全退出equals/hashCode,要么整个链(因此,类及其所有子类)需要正确地重写hashCode和equals,除非正确地检测父类,否则您真的不能这样做 合同规定,这必须始终正确:
- a、 等于(b)->b等于(a)
- a、 等于(b)和b.等于(c)->a.等于(c)
- a、 等于(a)
- a、 等于(b)->a.hashCode()==b.hashCode()。(注意,反过来不一定是真的;相等的hashcode并不意味着对象相等)
canEqual
方法。摆脱上述有色困境的方法是区分“我在扩展,并添加新属性”和“我在扩展,但从语义上讲,这些东西仍然是完全相同的东西,没有新属性”。ColoredArrayList是前一种情况:它是添加新属性的扩展。canEqual的思想是创建一个单独的方法来表示这一点,这让ArrayList明白:I不能等于任何ColoredArrayList实例,即使所有元素都相同。然后我们可以再次遵守合同。ArrayList没有这个系统,因此,考虑到您无法更改ArrayList的源代码,您将陷入困境:它是不可修复的。但如果您编写自己的类层次结构,则可以添加它
Lombok项目负责为您添加equals和hashCode。即使您不想使用它,您也可以查看它生成了什么,并在您自己的代码中复制它。这也将消除声纳发出的警告。请参阅–这还向您展示了如何使用canEqual
概念来避免ColoredArrayList困境
在这里,您可以在不添加新属性的情况下创建子类,因此,实际上不需要替换hashCode和equals。但声纳不知道这一点。让我们看看规则: 扩展重写等于的类并添加字段而不重写 在子类中等于,并且您将承担非等效的风险 子类的实例被视为相等的,因为只有 在相等性测试中将考虑超类字段 Sonar指出的是,不平等的对象被视为平等的风险,因为在子类中调用
equals
时,如果没有正确的实现,将只计算父类字段
不合规代码示例(来自文档)
兼容解决方案(也来自文档)
我同意你的观点,声纳可能不够复杂,无法在运行时看到发生了什么,因此它指向代码的气味
你需要担心的是不要打破等于
和哈希码
的合同,你的方法是动态的,声纳可能没有考虑到这一点。- 重写子类中的equals()和hashcode()方法将有效 考虑子类成员(变量),这也有助于 使用集合框架和映射实例的子类型查找 收集框架操作期间的右内存空间(bucket)(例如: 保存/检索) 从这里的超类继承可能会丢失到的子类成员 有效地生成hashcode/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;
}
}