Выводы
Разобрав четыре произвольно выбранных примера реализаций equals (), что мы делаем?
Прежде всего: в реализации функции equals () существует два существенно разных способа выполнения проверки на совпадение типов. Класс может разрешить сравнение смешанного типа между объектами супер- и подклассов с помощью оператора instanceof, или класс может обрабатывать объекты другого типа как не равные с помощью теста getClass (). Приведенные выше примеры прекрасно иллюстрируют, что реализации equals () с использованием getClass (), как правило, более устойчивы, чем эти реализации, использующие instanceof.
Тест instanceof корректен только для конечных классов или если хотя бы метод equals () является окончательным в суперклассе. Последнее по существу подразумевает, что ни один подкласс не должен расширять состояние суперкласса, но может добавлять только функциональные возможности или поля, которые не имеют отношения к состоянию и поведению объекта, такие как переходные или статические поля.
Реализации, использующие тест getClass (), с другой стороны, всегда соответствуют контракту equals (); они правильные и крепкие. Однако они семантически сильно отличаются от реализаций, в которых используется экземпляр теста. Реализации, использующие getClass (), не позволяют сравнивать подкласс с объектами суперкласса, даже когда подкласс не добавляет никаких полей и даже не хочет переопределять equals (). Такое «тривиальное» расширение класса будет, например, добавлением метода debug-print в подкласс, определенный именно для этой «тривиальной» цели. Если суперкласс запрещает сравнение смешанного типа с помощью проверки getClass (), то тривиальное расширение не будет сопоставимо с его суперклассом. Является ли это проблемой, полностью зависит от семантики класса и цели расширения.
x instanceof SomeClass
ложно, еслиx
естьnull
. Следовательно, второй синтаксис не нуждается в нулевой проверке.