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

Ответ 1

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

Ответ 2

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

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

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

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

Ответ 3

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

Выводы

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

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

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

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

Ответ 4

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

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

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

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

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

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

Ответ 5

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

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

Bloch имеет еще один важный совет в Эффективный Java Second издание:

  • Пункт 17: Дизайн и документ для наследования или запрет

Ответ 6

Исправьте меня, если я ошибаюсь, но 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

Ответ 7

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

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

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

class Organism
{
}

class Gorilla extends Organism
{
}

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

Ответ 8

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

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

if ( ! (obj instanceof MyClass) ) { return false; }

Ответ 9

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

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

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

Пока

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

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

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

Ответ 10

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

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

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

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

Ответ 11

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