Есть ли причина предпочесть getClass () instanceof при генерации .equals ()?
Я использую Eclipse для генерации .equals()
и .hashCode()
, и есть опция «Использовать 'instanceof' для сравнения типов». По умолчанию этот параметр не установлен и используется .getClass()
для сравнения типов. Есть ли какой - либо причине я предпочитаю .getClass()
более instanceof
?
Без использования instanceof
:
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Использование instanceof
:
if (obj == null)
return false;
if (!(obj instanceof MyClass))
return false;
Обычно я проверяю эту instanceof
опцию, а затем захожу и снимаю отметку " if (obj == null)
". (Это излишне, поскольку нулевые объекты всегда терпят неудачу instanceof
.) Есть ли причина, по которой это плохая идея?
Ответов (11)11
Если вы используете instanceof
, делая вашу equals
реализацию final
сохранит контракт симметрии метода: x.equals(y) == y.equals(x)
. Если это final
кажется ограничивающим, внимательно изучите свое понятие эквивалентности объектов, чтобы убедиться, что ваши замещающие реализации полностью поддерживают контракт, установленный Object
классом.
Поправьте меня, если я ошибаюсь, но getClass () будет полезен, если вы хотите убедиться, что ваш экземпляр НЕ является подклассом класса, с которым вы сравниваете. Если вы используете instanceof в этой ситуации, вы НЕ можете этого знать, потому что:
class A { }
class B extends A { }
Object oA = new A();
Object oB = new B();
oA instanceof A => true
oA instanceof B => false
oB instanceof A => true // <================ HERE
oB instanceof B => true
oA.getClass().equals(A.class) => true
oA.getClass().equals(B.class) => false
oB.getClass().equals(A.class) => false // <===============HERE
oB.getClass().equals(B.class) => true
Анжелика Лангерс « Секреты равных» разбирается в этом с долгим и подробным обсуждением нескольких распространенных и хорошо известных примеров, в том числе Джошем Блохом и Барбарой Лисков, обнаруживая пару проблем в большинстве из них. Кроме того, она попадает в instanceof
против getClass
. Некоторые цитаты из него
Выводы
К чему мы пришли, проанализировав четыре произвольно выбранных примера реализации equals ()?
Прежде всего: есть два существенно разных способа выполнения проверки соответствия типов в реализации equals (). Класс может разрешать сравнение смешанного типа между объектами суперкласса и подкласса с помощью оператора instanceof, или класс может обрабатывать объекты другого типа как не равные с помощью теста getClass (). Приведенные выше примеры хорошо иллюстрируют, что реализации equals () с использованием getClass (), как правило, более надежны, чем реализации с использованием instanceof.
Тест instanceof верен только для конечных классов или если хотя бы метод equals () является final в суперклассе. Последнее по существу подразумевает, что ни один подкласс не должен расширять состояние суперкласса, а может только добавлять функции или поля, которые не имеют отношения к состоянию и поведению объекта, например переходные или статические поля.
С другой стороны, реализации, использующие тест getClass (), всегда подчиняются контракту equals (); они правильные и надежные. Однако семантически они сильно отличаются от реализаций, использующих тест instanceof. Реализации, использующие getClass (), не позволяют сравнивать объекты подкласса с объектами суперкласса, даже если подкласс не добавляет никаких полей и даже не хочет переопределять equals (). Такое «тривиальное» расширение класса могло бы, например, быть добавлением метода отладки-печати в подкласс, определенный именно для этой «тривиальной» цели. Если суперкласс запрещает сравнение смешанных типов с помощью проверки getClass (), то тривиальное расширение не будет сопоставимо с его суперклассом. Является ли это проблемой, полностью зависит от семантики класса и цели расширения.
Фактически instanceof проверяет, принадлежит ли объект какой-либо иерархии или нет. Пример: объект Car принадлежит классу Vehical. Таким образом, "новый экземпляр Car () Vehical" возвращает истину. А «new Car (). GetClass (). Equals (Vehical.class)» возвращает false, хотя объект Car принадлежит классу Vehical, но относится к отдельному типу.
Джош Блох поддерживает ваш подход:
Причина, по которой я предпочитаю этот
instanceof
подход, заключается в том, что при его использовании уgetClass
вас есть ограничение, согласно которому объекты равны только другим объектам того же класса, того же типа времени выполнения. Если вы расширите класс и добавите к нему пару безобидных методов, затем проверьте, равен ли какой-либо объект подкласса объекту суперкласса, даже если объекты равны во всех важных аспектах, вы получите удивительный ответ, что они не равны. Фактически, это нарушает строгую интерпретацию принципа подстановки Лискова и может привести к очень неожиданному поведению. В Java это особенно важно, потому что большинство коллекций (HashTable
и т. д.) основаны на методе равенства. Если вы поместите член суперкласса в хеш-таблицу в качестве ключа, а затем посмотрите его, используя экземпляр подкласса, вы не найдете его, потому что они не равны.
См. Также этот SO-ответ .
Об этом также говорится в главе 3 « Эффективная Java» .
Если вы хотите, чтобы совпадал только этот класс, используйте getClass() ==
. Если вы хотите сопоставить подклассы, instanceof
это необходимо.
Кроме того, instanceof не будет соответствовать нулю, но его можно безопасно сравнивать с нулем. Таким образом, вам не нужно проверять его на нуль.
if ( ! (obj instanceof MyClass) ) { return false; }
Это зависит от того, учитываете ли вы, равен ли подкласс данного класса своему родительскому классу.
class LastName
{
(...)
}
class FamilyName
extends LastName
{
(..)
}
здесь я бы использовал instanceof, потому что я хочу, чтобы LastName сравнивался с FamilyName
class Organism
{
}
class Gorilla extends Organism
{
}
здесь я бы использовал getClass, потому что класс уже говорит, что эти два экземпляра не эквивалентны.
instanceof работает с объектами того же класса или его подклассов
Вы можете использовать его, чтобы проверить, является ли объект экземпляром класса, экземпляром подкласса или экземпляром класса, реализующего определенный интерфейс.
ArryaList и RoleList являются экземплярами List
В то время как
getClass () == o.getClass () будет истинным, только если оба объекта (this и o) принадлежат к одному и тому же классу.
Итак, в зависимости от того, что вам нужно сравнить, вы можете использовать одно или другое.
Если ваша логика такова: «Один объект равен другому, только если они оба принадлежат к одному классу», вам следует выбрать «равно», что, как мне кажется, в большинстве случаев.
Причина использования getClass
- обеспечить симметричность equals
контракта. Из равно 'JavaDocs:
Он симметричен: для любых ненулевых ссылочных значений x и y x.equals (y) должен возвращать true тогда и только тогда, когда y.equals (x) возвращает true.
Используя instanceof, можно не быть симметричным. Рассмотрим пример: Dog расширяет Animal. Животное equals
делает instanceof
проверку животных. Собаки equals
делает instanceof
проверку собаки. Дайте Animal a и Dog d (с такими же полями):
a.equals(d) --> true
d.equals(a) --> false
Это нарушает свойство симметрии.
Чтобы строго соблюдать договор равных, должна быть обеспечена симметрия, и, следовательно, класс должен быть одинаковым.
Это что-то вроде религиозной дискуссии. У обоих подходов есть свои проблемы.
- Используйте instanceof, и вы никогда не сможете добавлять важные члены в подклассы.
- Используйте getClass, и вы нарушите принцип подстановки Лискова.
У Блоха есть еще один важный совет в Effective Java Second Edition :
- Пункт 17: Оформить и оформить документы на наследование или запретить его
У обоих методов есть свои проблемы.
Если подкласс меняет идентичность, вам необходимо сравнить их фактические классы. В противном случае вы нарушите свойство симметрии. Например, разные типы Person
s не следует считать эквивалентными, даже если они имеют одинаковое имя.
Однако некоторые подклассы не меняют идентичность, и их нужно использовать instanceof
. Например, если у нас есть набор неизменяемых Shape
объектов, то Rectangle
длина и ширина 1 должны быть равны единице Square
.
На практике я думаю, что первый случай более вероятен. Обычно создание подклассов является фундаментальной частью вашей личности, и если вы в точности похожи на своих родителей, за исключением того, что вы можете делать одну мелочь, это не делает вас равными.