Как реализовать equals в Java, чтобы избежать ClassCastException при сравнении с объектом другого типа
Что проверяют
Знает ли кандидат контракт equals и умеет ли безопасно проверять тип перед приведением. Отдельно смотрят, понимает ли он разницу между getClass() и instanceof и почему hashCode переопределяется в паре.
Как отвечать
ClassCastException в equals возникает ровно по одной причине: приведение сделано до проверки типа. Порядок действий такой.
- Быстрая проверка
this == obj. - Проверка типа:
instanceofлибо сравнениеgetClass(). - Приведение и сравнение значимых полей.
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof MyClass other)) return false; // Java 16+
return id == other.id && Objects.equals(name, other.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}instanceof возвращает false для null, поэтому отдельная проверка на null тут лишняя. А вот при варианте с getClass() она обязательна, иначе будет NullPointerException:
if (obj == null || getClass() != obj.getClass()) return false;Разница между подходами содержательная, её и хотят услышать:
getClass()строго требует совпадения классов. Симметрия гарантирована, но объект подкласса никогда не равен объекту базового класса, даже если подкласс не добавил ни одного поля. Это нарушает принцип подстановки Лисков.instanceofдопускает равенство с подклассами. Гибче, но легко ломает симметрию: если подкласс добавил поле и переопределилequals, тоparent.equals(child)вернётtrue, аchild.equals(parent)вернётfalse. Лечится тем, чтоequalsделаютfinalлибо класс объявляютfinal.
Что ещё входит в контракт: рефлексивность, симметричность, транзитивность, консистентность и x.equals(null) == false. Нарушение любого пункта ломает HashSet и HashMap тихо, без исключений.
Про поля: примитивы сравниваются через ==, но double и float через Double.compare/Float.compare из-за NaN и -0.0, массивы через Arrays.equals, ссылки через Objects.equals, который сам разбирается с null.
hashCode обязателен вместе с equals: равные объекты должны давать равный хеш, иначе объект не найдётся в собственной хеш-таблице. Objects.hash удобен, но аллоцирует массив, в горячем коде пишут 31 * result + ... руками.
Где спотыкаются
Пишут public boolean equals(MyClass o). Это перегрузка, а не переопределение: коллекции зовут версию с параметром Object, и объекты «внезапно» перестают находиться в множестве. Аннотация @Override ловит эту ошибку на компиляции.