Предупреждение 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)
не был переопределен.