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