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并不意味着对象相等)
面对类层次结构,契约真的很难保证!假设我们使用现有的java.util.ArrayList并使用“color”的概念对其进行子类化。现在我们可以有一个蓝色的ArrayList,或者一个红色的ArrayList。如果说蓝色ArrayList绝对不应该等于红色ArrayList,那将是非常有意义的,除了。。ArrayList本身的equals impl(您无法更改)有效地定义了您根本无法使用这样的属性扩展ArrayList:如果调用a.equals(b),其中a是空的ArrayList,b是一些空列表(例如,一个空的红色ArrayList),它只会检查其中每个成员的相等性,考虑到它们都是空的,这一点就非常正确了。因此,空的正常arraylist等于空的红色arraylist和空的蓝色arraylist,因此合同规定,空的红色arraylist必须等于空的蓝色arraylist。从这个意义上说,声纳在这里被破坏了。有一个问题,它是不可修复的不可能用java编写ColoredArrayList的概念

然而,只有当层次结构中的每个类都在板上时,才有解决方案。这是
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;
  }
}