Любая причина, чтобы предпочесть getClass () над instanceof при создании .equals ()?

174

Я использую 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.) Есть ли причина, по которой это плохая идея?

койка
источник
1
Выражение x instanceof SomeClassложно, если xесть null. Следовательно, второй синтаксис не нуждается в нулевой проверке.
RDs
5
@rds Да, абзац сразу после фрагмента кода также говорит об этом. Это в фрагменте кода, потому что это то, что генерирует Eclipse.
Кип

Ответы:

100

Если вы используете instanceof, делая вашу equalsреализацию finalсохранит контракт симметрии метода: x.equals(y) == y.equals(x). Если это finalкажется ограничительным, внимательно изучите ваше представление об эквивалентности объектов, чтобы убедиться, что ваши основные реализации полностью поддерживают контракт, установленный Objectклассом.

Эриксон
источник
именно это пришло мне в голову, когда я прочитал цитату Джоша Блоха выше. +1 :)
Йоханнес Шауб -
13
определенно ключевое решение. должна применяться симметрия, а instanceof позволяет очень легко случайно оказаться асимметричным
Скотт Стэнчфилд,
Я также добавил бы, что «если finalкажется ограничительным» (для обоих equalsи hashCode), тогда используйте getClass()равенство вместо instanceofтого, чтобы сохранить требования equalsконтракта по симметрии и транзитивности .
Shadow Man
176

Джош Блох одобряет ваш подход:

Причина, по которой я предпочитаю этот instanceofподход, заключается в том, что когда вы используете этот getClassподход, у вас есть ограничение, что объекты равны только другим объектам того же класса, того же типа времени выполнения. Если вы расширяете класс и добавляете к нему пару безобидных методов, то проверяете, равен ли какой-либо объект подкласса объекту суперкласса, даже если объекты равны во всех важных аспектах, вы получите Удивительный ответ, что они не равны. Фактически это нарушает строгую интерпретацию принципа подстановки Лискова и может привести к очень неожиданному поведению. В Java это особенно важно, потому что большинство коллекций (HashTableи т. д.) основаны на методе равных. Если вы поместите член суперкласса в хеш-таблицу в качестве ключа, а затем просмотрите его, используя экземпляр подкласса, вы не найдете его, потому что они не равны.

Смотрите также этот так ответ .

Эффективная глава 3 Java также покрывает это.

Майкл Майерс
источник
18
+1 Каждый, кто занимается Java, должен прочитать эту книгу как минимум 10 раз!
Андре
16
Проблема с подходом instanceof заключается в том, что он нарушает «симметричное» свойство Object.equals (), поскольку становится возможным, что x.equals (y) == true, но y.equals (x) == false, если x.getClass ()! = y.getClass (). Я предпочитаю не нарушать это свойство без крайней необходимости (т. Е. Переопределение equals () для управляемых или прокси-объектов).
Кевин Ситце
8
Подход instanceof уместен тогда и только тогда, когда базовый класс определяет, что должно означать равенство между объектами подкласса. Использование getClassне нарушает LSP, поскольку LSP просто относится к тому, что можно сделать с существующими экземплярами, а не к тому, какие типы экземпляров могут быть созданы. Возвращаемый класс getClassявляется неизменным свойством экземпляра объекта. LSP не подразумевает, что должна быть возможность создать подкласс, где это свойство указывает любой класс, кроме того, который его создал.
суперкат
66

Анжелика Лангерс Секреты равных вступает в это с долгим и подробным обсуждением нескольких распространенных и известных примеров, в том числе Джошом Блохом и Барбарой Лисков, обнаруживших пару проблем в большинстве из них. Она также попадает в instanceofпротив getClass. Некоторая цитата из этого

Выводы

Разобрав четыре произвольно выбранных примера реализаций equals (), что мы делаем?

Прежде всего: в реализации функции equals () существует два существенно разных способа выполнения проверки на совпадение типов. Класс может разрешить сравнение смешанного типа между объектами супер- и подклассов с помощью оператора instanceof, или класс может обрабатывать объекты другого типа как не равные с помощью теста getClass (). Приведенные выше примеры прекрасно иллюстрируют, что реализации equals () с использованием getClass (), как правило, более устойчивы, чем эти реализации, использующие instanceof.

Тест instanceof корректен только для конечных классов или если хотя бы метод equals () является окончательным в суперклассе. Последнее по существу подразумевает, что ни один подкласс не должен расширять состояние суперкласса, но может добавлять только функциональные возможности или поля, которые не имеют отношения к состоянию и поведению объекта, такие как переходные или статические поля.

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

Йоханнес Шауб - Литб
источник
61

Основанием для использования getClassявляется обеспечение симметричного свойства equalsдоговора. Из равных JavaDocs:

Это симметрично: для любых ненулевых ссылочных значений x и y x.equals (y) должен возвращать true, если и только если y.equals (x) возвращает true.

Используя instanceof, можно не быть симметричным. Рассмотрим пример: собака расширяет животное. Животное equalsделает instanceofпроверку животных. Собаки equalsделает instanceofпроверку собаки. Дайте Animal a и Dog d (с другими полями то же самое):

a.equals(d) --> true
d.equals(a) --> false

Это нарушает симметричное свойство.

Чтобы строго следовать контракту равных, симметрия должна быть обеспечена, и, таким образом, класс должен быть одинаковым.

Стив Куо
источник
8
Это первый четкий и краткий ответ на этот вопрос. Пример кода стоит тысячи слов.
Джонрайд
Я просматривал все подробные ответы, если вы спасли день. Простой, лаконичный, элегантный и совершенно лучший ответ на этот вопрос.
1
Как вы упомянули, существует требование симметрии. Но есть и требование «транзитивности». Если a.equals(c)и b.equals(c), то a.equals(b)(наивный подход создания Dog.equalsтолько return super.equals(object)тогда, когда !(object instanceof Dog)проверка дополнительных полей, когда это экземпляр Dog, не нарушил бы симметрию, но нарушил бы транзитивность)
Shadow Man
Чтобы быть в безопасности, вы можете использовать getClass()проверку на равенство, или вы можете использовать instanceofпроверку, если делаете ваши equalsи hashCodeметоды final.
Shadow Man
26

Это что-то вроде религиозной дискуссии. Оба подхода имеют свои проблемы.

  • Используйте instanceof, и вы никогда не сможете добавить значимых членов в подклассы.
  • Используйте getClass, и вы нарушаете принцип подстановки Лискова.

У Bloch есть еще один важный совет в Effective Java Second Edition :

  • Пункт 17: Дизайн и документ для наследования или запретить его
Макдауэлл
источник
1
Ничто в использовании getClassне нарушит LSP, если только базовый класс не задокументирует специально средство, с помощью которого можно сделать экземпляры разных подклассов одинаковыми. Какое нарушение LSP вы видите?
суперкат
Возможно, это следует перефразировать как Use getClass, и вы оставите подтипы в опасности нарушений LSP. У Блоха есть пример в Effective Java, который также обсуждается здесь . Его обоснование во многом заключается в том, что это может привести к неожиданному поведению разработчиков, реализующих подтипы, которые не добавляют состояние. Я понимаю вашу точку зрения - документация является ключевой.
Макдауэлл
2
Единственный раз, когда два экземпляра различных классов должны сообщать о себе, равны, если они наследуются от общего базового класса или интерфейса, который определяет, что означает равенство [философия, которую Java сомнительно применила к некоторым интерфейсам коллекций]. Если в контракте базового класса не указано, что getClass()его не следует считать значимым, за исключением того факта, что рассматриваемый класс может быть преобразован в базовый класс, возврат из getClass()следует рассматривать как любое другое свойство, которое должно совпадать, чтобы экземпляры были равными.
суперкат
1
@eyalzba Где instanceof_ используется, если подкласс добавляет членов к контракту равенства, это нарушит требование симметричного равенства. Использование подклассов getClass никогда не может быть равным родительскому типу. Не то чтобы вы все равно переопределяли равные, но это компромисс, если вы это сделаете.
Макдауэлл
2
«Дизайн и документация для наследования или запрета», я думаю, это очень важно. Насколько я понимаю, единственный раз, когда вы должны переопределить equals, это если вы создаете тип неизменяемого значения, и в этом случае класс должен быть объявлен как final. Поскольку наследования не будет, вы можете применять равные по мере необходимости. В тех случаях, когда вы разрешаете наследование, объекты должны сравниваться по ссылочному равенству.
TheSecretSquad
23

Поправьте меня, если я ошибаюсь, но 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
TraderJoeChicago
источник
Для меня это причина
karlihnos
5

Если вы хотите убедиться, что только этот класс будет соответствовать, используйте getClass() ==. Если вы хотите соответствовать подклассам, то instanceofэто необходимо.

Кроме того, instanceof не будет совпадать с нулем, но безопасно сравнивать с нулем. Так что вам не нужно проверять это на ноль.

if ( ! (obj instanceof MyClass) ) { return false; }
Clint
источник
Относительно вашего второго пункта: он уже упомянул это в вопросе. «Я обычно проверяю параметр instanceof, а затем захожу и убираю проверку« if (obj == null) ».»
Майкл Майерс
5

Это зависит от того, считаете ли вы, что подкласс данного класса равен его родителю.

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

здесь я бы использовал instanceof, потому что я хочу, чтобы LastName сравнивалось с FamilyName

class Organism
{
}

class Gorilla extends Organism
{
}

здесь я бы использовал 'getClass', потому что класс уже говорит, что два экземпляра не эквивалентны.

пьер
источник
3

instanceof работает для инстинктов того же класса или его подклассов

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

ArryaList и RoleList являются экземплярами List

Пока

getClass () == o.getClass () будет истинным, только если оба объекта (this и o) принадлежат к одному и тому же классу.

Таким образом, в зависимости от того, что вам нужно сравнить, вы можете использовать один или другой.

Если ваша логика такова: «Один объект равен другим, только если они оба относятся к одному и тому же классу», вы должны выбрать «равно», что, как я думаю, в большинстве случаев.

OscarRyz
источник
3

Оба метода имеют свои проблемы.

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

Однако некоторые подклассы не меняют идентичность, и их нужно использовать instanceof. Например, если у нас есть куча неизменных Shapeобъектов, тогда a Rectangleс длиной и шириной 1 должен быть равен единице Square.

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

Джеймс
источник
-1

На самом деле экземпляр проверки того, принадлежит ли объект какой-либо иерархии или нет. пример: объект Car относится к классу Vehical. Таким образом, «new Car () instance of Vehical» возвращает true. И "new Car (). GetClass (). Equals (Vehical.class)" возвращает false, хотя объект Car принадлежит классу Vehical, но он классифицируется как отдельный тип.

Akash5288
источник