Любая причина, чтобы предпочесть 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)msgstr "проверка. (Это избыточно, так как нулевые объекты всегда будут терпеть неудачу instanceof.) Есть ли причина, по которой это плохая идея?

11 ответов

Решение

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

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

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

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

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

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

Выводы

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

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

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

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

Причина использования 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

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

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

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

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

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

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

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

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

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

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

class Organism
{
}

class Gorilla extends Organism
{
}

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

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

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

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

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

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

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

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

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

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

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

В то время как

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

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

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

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

Другие вопросы по тегам