Сначала нужно решить, действительно ли вам нужно переопределять equals(). Реализация по умолчанию делает объект равным только самому себе (сравнение на идентичность). Это имеет смысл, если у вашего класса не бывает отдельных, но логически одинаковых экземпляров.

Если два экземпляра всё-таки могут быть равны, equals() нужно переопределять. Реализация должна соблюдать контракт: это отношение эквивалентности (рефлексивность, транзитивность, симметричность), ни один объект не равен null.

Рефлексивность. первым делом проверим, не идентичен ли переданный объект текущему. Если да – сразу вернем true.

Неравенство null. Если аргумент null – сразу вернем false.

Симметричность. Если мы допускаем наследование и расширение метода equals(), в наследнике может появиться дополнительная логика, которая сделает !other.equals(this) при this.equals(other). Проще всего избежать этого, добавив сравнение типов. Если типы не равны – сразу вернем false. Почему не надо использовать instanceof.

Транзитивность. Оператор == обладает свойствами транзитивности и симметричности. Далее мы сравниваем на равенство все примитивные свойства. Для ссылочных типов этими характеристиками по контракту обладает equals – для сравнения ссылочных типов пользуемся им.

Речь здесь идет о логических свойствах. Фактически одно логическое свойство может быть представлено несколькими полями класса, или же может вычисляться на лету. Некоторые поля служат для внутренних технических нужд, и не имеют отношения к логическому состоянию. Такие поля обычно исключают из сравнения.