Есть ли причина предпочесть 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)

Решение

Если вы используете 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 .

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