Любая причина, чтобы предпочесть 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; }
Оба метода имеют свои проблемы.
Если подкласс меняет идентичность, то вам нужно сравнить их фактические классы. В противном случае вы нарушаете симметричное свойство. Например, разные типы Person
s не следует считать эквивалентными, даже если они имеют одинаковые имена.
Тем не менее, некоторые подклассы не меняют идентичность, и они должны использовать 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, но он классифицируется как отдельный тип.