Будь-яка причина віддати перевагу getClass () над instanceof при генерації .equals ()?


174

Я використовую 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
Вираз x instanceof SomeClassпомилковий, якщо xє null. Отже, другий синтаксис не потребує перевірки нуля.
rds

5
@rds Так, абзац відразу після фрагмента коду говорить і про це. Він знаходиться в фрагменті коду, оскільки саме це породжує Eclipse.
Кіп

Відповіді:


100

Якщо ви використовуєте instanceof, роблячи вашу equalsреалізацію finalзбереже контракт симетрії методу: x.equals(y) == y.equals(x). Якщо вам finalздається обмежувальним, уважно вивчіть своє поняття еквівалентності об'єкта, щоб переконатися, що ваші переважні реалізації повністю підтримують контракт, встановлений Objectкласом.


саме це мені прийшло в голову, коли я читав цитату josh bloch вище. +1 :)
Йоганнес Шауб - ліб

13
безумовно, ключове рішення. повинна застосовуватися симетрія, і екземпляр дозволяє дуже легко бути випадково асиметричним
Скотт Стенчфілд

Я також додав би, що "якщо finalздається обмежуючим" (і на обох equalsі hashCode), тоді використовуйте getClass()рівність замість instanceofтого, щоб зберегти вимоги до симетричності та транзитивності equalsконтракту.
Shadow Man

176

Джош Блох виступає за ваш підхід:

Причиною того, що я віддаю перевагу instanceofпідходу, є те, що при використанні getClassпідходу у вас є обмеження, що об'єкти рівні лише іншим об'єктам того ж класу, того ж типу часу виконання. Якщо ви розширите клас і додасте до нього пару нешкідливих методів, то перевірте, чи є якийсь об'єкт підкласу рівним об’єкту надкласового класу, навіть якщо об'єкти рівні у всіх важливих аспектах, ви отримаєте дивна відповідь, що вони не рівні. Насправді це порушує суворе тлумачення принципу заміни Ліскова і може призвести до дуже дивної поведінки. Для Java це особливо важливо, оскільки більшість колекцій (HashTableтощо) засновані на методі рівних. Якщо ви помістите члена суперкласу в хеш-таблицю як ключ, а потім шукаєте його за допомогою екземпляра підкласу, його ви не знайдете, оскільки вони не рівні.

Дивіться також цю відповідь ТА .

Ефективна глава 3 розділу Java також охоплює це.


18
+1 Усі, хто займається Java, повинні прочитати цю книгу принаймні 10 разів!
Андре

16
Проблема підходу instanceof полягає в тому, що він порушує "симетричне" властивість Object.equals () тим, що стає можливим, що x.equals (y) == true, але y.equals (x) == false, якщо x.getClass ()! = y.getClass (). Я вважаю за краще не порушувати цю властивість, якщо це абсолютно не потрібно (тобто переосмислення рівнянь () для керованих або проксі-об'єктів).
Кевін Сітце

8
Підхід instanceof є правильним, коли і лише коли базовий клас визначає, що має означати рівність між об'єктами підкласу. Використання getClassне порушує LSP, оскільки LSP просто стосується того, що можна зробити з існуючими екземплярами, а не з тими, які можуть бути побудовані. Клас, що повертається, getClass- це незмінна властивість об'єкта-об'єкта. LSP не означає, що повинно бути можливим створити підклас, де це властивість вказує на будь-який клас, окрім класу, який його створив.
supercat

66

Анжеліка Лангерс " Секрети рівних" потрапляє в цю історію з довгим і детальним обговоренням декількох поширених і відомих прикладів, зокрема Джоша Блоха та Барбари Ліськов, виявивши кілька проблем у більшості з них. Вона також потрапляє в instanceofпроти getClass. Деякі цитати з нього

Висновки

Розділивши чотири довільно обрані приклади реалізації рівностей (), що ми робимо?

Перш за все: є два суттєво різні способи виконання перевірки відповідності типу в реалізації рівняння (). Клас може дозволяти зіставляти змішані типи об’єктів над- і підкласу за допомогою оператора instanceof, або клас може розглядати об'єкти іншого типу як нерівні за допомогою тесту getClass (). Наведені вище приклади добре проілюстрували, що реалізація рівнянь () за допомогою getClass (), як правило, більш надійна, ніж ті, що реалізують, використовуючи instanceof.

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

Реалізація, що використовує тест getClass (), з іншого боку, завжди відповідає договору equals (); вони правильні та надійні. Однак вони семантично дуже відрізняються від реалізацій, які використовують тест instanceof. Реалізації, що використовують getClass (), не дозволяють порівнювати під- з об'єктами надкласового класу, навіть коли підклас не додає жодних полів і навіть не хотів би замінити рівняння (). Таке "тривіальне" розширення класу, наприклад, було б додаванням методу друку налагодження у підкласі, визначеному саме для цієї "тривіальної" мети. Якщо суперклас забороняє порівняння змішаного типу через перевірку getClass (), то тривіальне розширення було б не порівнянне з його суперкласом. Чи повністю це проблема чи ні, залежить від семантики класу та мети розширення.


61

Підставою для використання getClassє забезпечення симетричної властивості equalsдоговору. З рівних JavaDocs:

Він симетричний: для будь-яких ненульових опорних значень x і y, x.equals (y) має повертати істину, якщо і лише тоді, коли y.equals (x) повертає істинне.

Використовуючи instanceof, можна не бути симетричним. Розглянемо приклад: Собака розтягує Тварину. Animal's equalsробить instanceofперевірку на Animal. Собака equalsробить instanceofперевірку Собаки. Дайте тварині a та собаці d (з іншими полями те саме):

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

Це порушує симетричну властивість.

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


8
Це перша чітка і лаконічна відповідь на це питання. Зразок коду коштує тисячу слів.
Johnride

Я переглядав усі інші багатослівні відповіді, в яких ви врятували день. Проста, лаконічна, елегантна та абсолютно найкраща відповідь на це питання.

1
Існує вимога симетрії, як ви згадали. Але є і вимога "транзитивності". Якщо a.equals(c)і b.equals(c), то a.equals(b)(наївний підхід зробити Dog.equalsпросто return super.equals(object)тоді, !(object instanceof Dog)але перевірка зайвих полів, коли це екземпляр Собаки, не порушила б симетрію, але порушила б транзитивність)
Shadow Man

Щоб бути безпечним, ви можете використовувати getClass()перевірку рівності, або ви можете використовувати instanceofперевірку, якщо зробити свої equalsта hashCodeметоди final.
Shadow Man

26

Це щось із релігійної дискусії. Обидва підходи мають свої проблеми.

  • Використовуйте instanceof, і ви ніколи не можете додавати значних членів до підкласів.
  • Використовуйте getClass і ви порушуєте принцип заміни Ліскова .

У Bloch є ще одна відповідна порада в Ефективній Java Second Edition :

  • Пункт 17: Проектувати та документувати спадщину або забороняти це

1
Ніщо про використання getClassне порушить LSP, якщо тільки базовий клас спеціально не задокументував засоби, за допомогою яких можна було б зробити екземпляри різних підкласів, які порівнюються рівними. Яке порушення LSP ви бачите?
supercat

Можливо, це слід перефразовувати як Use getClass, і ви залишаєте підтипи під загрозою порушення LSP. Bloch має приклад в Ефективній Java - також обговорюється тут . Його обґрунтування багато в чому полягає в тому, що це може спричинити дивовижну поведінку для розробників, що реалізують підтипи, які не додають стану. Я вважаю, що документація є ключовою.
МакДауелл

2
Єдиний раз, коли два екземпляри різних класів коли-небудь повинні повідомляти про себе, є рівними, якщо вони успадковують від загального базового класу або інтерфейсу, який визначає, що означає рівність [філософію, яку Java застосував, сумнівно, IMHO, до деяких інтерфейсів колекції]. Якщо договір про базовий клас не говорить про те, що getClass()не слід вважати значимим за винятком того, що відповідний клас може бути конвертованим у базовий клас, повернення з нього getClass()повинно розглядатися як будь-яке інше властивість, яке повинно відповідати, щоб випадки були рівними.
supercat

1
@eyalzba Якщо instanceof_ використовується, якщо підклас додав членів до договору рівності, це порушило б вимогу симетричної рівності. Використання підкласів getClass ніколи не може бути рівним батьківському типу. Не те, що тобі все одно дорівнюватиме рівних, але це компроміс, якщо ти це робиш.
Макдауелл

2
"Дизайн та документ на спадщину чи забороняти" Я вважаю, що це дуже важливо. Я розумію, що єдиний час, коли ви повинні переосмислити рівність, - це якщо ви створюєте тип незмінного значення, і в цьому випадку клас слід оголосити остаточним. Оскільки спадщини не буде, ви можете реалізовувати рівних за потребою. У тих випадках, коли ви дозволяєте успадкування, об'єкти повинні порівнюватися за рівнем відліку.
TheSecretSquad

23

Виправте мене, якщо я помиляюся, але 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

Для мене це причина
karlihnos

5

Якщо ви хочете переконатися, що відповідатиме лише той клас, тоді використовуйте getClass() ==. Якщо ви хочете відповідати підкласам, тоді instanceofце потрібно.

Також, instanceof не збігатиметься з null, але його можна порівняти з null. Тож вам не доведеться це перевіряти нанівець.

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

Перегляньте свій другий пункт: Він уже згадував це у питанні. "Я зазвичай перевіряю опцію instanceof, а потім заходжу та знімаю галочку" if (obj == null) "."
Майкл Майерс

5

Це залежить, якщо врахувати, чи підклас даного класу дорівнює його батьківському.

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

тут я б використав 'instanceof', тому що я хочу, щоб LastName порівнювався з FamilyName

class Organism
{
}

class Gorilla extends Organism
{
}

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


3

instanceof працює на погляди того ж класу або його підкласів

Ви можете використовувати його для перевірки, чи об'єкт є екземпляром класу, екземпляром підкласу або екземпляром класу, який реалізує певний інтерфейс.

ArryaList і RoleList - це інстанції List

Поки

getClass () == o.getClass () буде істинним лише в тому випадку, якщо обидва об'єкти (це та o) належать точно до одного класу.

Тож залежно від того, що вам потрібно порівняти, ви могли використовувати те чи інше.

Якщо ваша логіка: "Один об'єкт дорівнює іншим, лише якщо вони обидва того ж класу", ви повинні перейти до "рівних", що, на мою думку, є більшістю випадків.


3

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

Якщо підклас змінює ідентичність, то вам потрібно порівняти їхні фактичні класи. В іншому випадку ви порушите симетричну властивість. Наприклад, різні типи Persons не повинні вважатися еквівалентними, навіть якщо вони мають однакове ім'я.

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

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


-1

Насправді випадки перевірки, де об'єкт належить до якоїсь ієрархії чи ні. наприклад: Об'єкт автомобіля належить до класу "Транспорт". Тож "новий екземпляр автомобіля") повертає істину. І "новий автомобіль (). GetClass (). Дорівнює (Vehical.class)" повертає помилкове значення, хоча об'єкт "Автомобіль" належить до класу "транспортні засоби", але його віднесено до окремого типу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.