Предупреждение Findbugs: метод Equals не должен принимать ничего о типе аргумента

При запуске FindBugs в моем проекте я получил несколько примеров ошибки, описанной выше.

А именно, мои переопределяющие версии равных приводят объект RHS к тому же типу, что и объект, в котором определена переопределяющая версия.

Однако я не уверен, возможен ли лучший дизайн, поскольку AFAIK Java не допускает отклонения в параметрах метода, поэтому невозможно определить какой-либо другой тип для параметра equals.

Я делаю что-то очень неправильно, или FindBugs слишком нетерпелив?

Другой способ сформулировать этот вопрос: каково правильное поведение, если объект, переданный равным, не является тем же типом, что и LHS: является ли это ложным или должно быть исключение?

Например:

public boolean equals(Object rhs)
{
    MyType rhsMyType = (MyType)rhs; // Should throw exception
    if(this.field1().equals(rhsMyType.field1())... // Or whatever
}

Ответ 1

Обычно при реализации equals вы можете проверить, равен ли класс аргумента (или совместим) с классом реализации перед его литьем. Что-то вроде этого:

if (getClass() != obj.getClass())
    return false;
MyObj myObj = (MyObj) obj;

Выполнение этого способа предотвратит предупреждение FindBugs.

Заметка для комментария:
Некоторые люди утверждают, что для проверки безопасности типа используйте instanceof вместо getClass. В этом есть большая дискуссия, в которую я пытался не попасть, когда я заметил, что вы можете проверить совместимость классов или, но я думаю, я не могу избежать этого. Это сводится к этому - если вы используете instanceof, вы можете поддерживать равенство между экземплярами класса и экземплярами его подкласса, но вы рискуете нарушить симметричный контракт equals. Обычно я бы рекомендовал не использовать instanceof, если вы не знаете, что вам это нужно, и вы знаете, что делаете. Для получения дополнительной информации см.:

Ответ 2

Вероятно, вы делаете что-то вроде этого:

public class Foo {
  // some code

  public void equals(Object o) {
    Foo other = (Foo) o;
    // the real equals code
  }
}

В этом примере вы принимаете что-то о аргументе equals(): вы предполагаете его тип Foo. Этого не должно быть! Вы также можете получить строку (в этом случае вы должны почти наверняка вернуть false).

Итак, ваш код должен выглядеть так:

public void equals(Object o) {
  if (!(o instanceof Foo)) {
    return false;
  }
  Foo other = (Foo) o;
  // the real equals code
}

(или используйте более строгие getClass() != o.getClass(), упомянутые Dave L.

Вы также можете посмотреть на него следующим образом:

Integer i = new Integer(42);
String s = "fourtytwo";
boolean b = i.equals(s);

Есть ли причина, по которой этот код должен бросать ClassCastException вместо того, чтобы нормально заканчивать и устанавливать b на false?

Бросание a ClassCastException в качестве ответа на .equals() не было бы разумным. Потому что, даже если это глупый вопрос ( "Конечно, String никогда не совпадает с Foo!" ), Он все еще действительный с совершенно точным ответом ( "no" == false).

Ответ 3

Я бы рекомендовал проигнорировать это предупреждение. На практике, если equals вызывается с объектом неожиданного класса, это почти наверняка ошибка, и вы хотите быстро ошибиться в ошибках.

Например, если у вас есть файлы ArrayList и вызывается files.contains( "MyFile.txt" ), было бы неплохо, если бы вы получили исключение ClassCastException. Вместо этого Java просто возвращает false, и это, вероятно, занимает много времени, пока вы не обнаружите эту ошибку.

Ответ 4

Я начинаю реализацию equals (Object) следующим образом:

if ((object == null) || !(object instaceof ThisClass)) {
    return false;
}

Это также предотвратит предупреждение FindBugs, но не будет автоматически возвращать false при сдаче подкласса ThisClass. Он также может считаться равным, особенно если его метод equals(Object) не был переопределен.