Любая причина, чтобы предпочесть getClass () над instanceof при создании .equals ()? - PullRequest
166 голосов
/ 27 февраля 2009

Я использую 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 ]

167 голосов
/ 27 февраля 2009

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

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

См. Также этот SO-ответ .

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

95 голосов
/ 28 февраля 2009

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

62 голосов
/ 28 февраля 2009

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

Выводы

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

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

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

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

55 голосов
/ 28 февраля 2009

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

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

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

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

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

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

26 голосов
/ 28 февраля 2009

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

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

Bloch имеет еще один полезный совет в Effective Java Second Edition :

  • Пункт 17: Дизайн и документ для наследования или запретить его
21 голосов
/ 26 октября 2011

Поправьте меня, если я ошибаюсь, но 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
5 голосов
/ 27 февраля 2009

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

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

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

class Organism
{
}

class Gorilla extends Organism
{
}

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

5 голосов
/ 27 февраля 2009

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

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

if ( ! (obj instanceof MyClass) ) { return false; }
3 голосов
/ 27 февраля 2009

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

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

ArryaList и RoleList являются instanceof List

Пока

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

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

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

2 голосов
/ 28 февраля 2009

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

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

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

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

...