Czy jest jakiś powód, aby preferować metodę getClass () zamiast instanceof podczas generowania .equals ()?

174

Używam Eclipse do generowania .equals()i .hashCode(), i jest opcja oznaczona jako „Użyj„ instanceof ”, aby porównać typy”. Domyślnie ta opcja jest odznaczona i używana .getClass()do porównywania typów. Czy jest jakiś powód powinien Wolę .getClass()ponad instanceof?

Bez użycia instanceof:

if (obj == null)
  return false;
if (getClass() != obj.getClass())
  return false;

Używając instanceof:

if (obj == null)
  return false;
if (!(obj instanceof MyClass))
  return false;

Zwykle zaznaczam instanceofopcję, a następnie wchodzę i usuwam if (obj == null)zaznaczenie. (Jest to zbędne, ponieważ puste obiekty zawsze zawodzą instanceof). Czy jest jakiś powód, dla którego to zły pomysł?

Wyrko
źródło
1
Wyrażenie x instanceof SomeClassjest fałszywe, jeśli xjest null. W związku z tym druga składnia nie wymaga sprawdzania wartości null.
rds
5
@rds Tak, akapit zaraz po fragmencie kodu również to mówi. Znajduje się we fragmencie kodu, ponieważ to właśnie generuje Eclipse.
Kip

Odpowiedzi:

100

Jeśli używasz instanceof, dzięki czemu equalswdrażanie finalzachowa umowę symetrii metody: x.equals(y) == y.equals(x). Jeśli finalwydaje się to restrykcyjne, dokładnie przeanalizuj swoje pojęcie równoważności obiektów, aby upewnić się, że nadpisujące implementacje w pełni obsługują kontrakt ustanowiony przez Objectklasę.

erickson
źródło
dokładnie to przyszło mi do głowy, kiedy przeczytałem powyższy cytat z książki Josha. +1 :)
Johannes Schaub - litb
13
zdecydowanie kluczowa decyzja. symetria musi obowiązywać, a instanceof bardzo ułatwia przypadkową asymetrię
Scott Stanchfield
Dodałbym również, że „jeśli finalwydaje się restrykcyjne” (w obu przypadkach equalsi hashCode), należy użyć getClass()równości zamiast instanceof, aby zachować wymagania dotyczące symetrii i przechodniości equalskontraktu.
Shadow Man,
176

Josh Bloch popiera Twoje podejście:

Powodem, dla którego preferuję to instanceofpodejście, jest to, że kiedy używasz tego getClasspodejścia, masz ograniczenie, że obiekty są równe tylko innym obiektom tej samej klasy, tego samego typu czasu wykonywania. Jeśli rozszerzysz klasę i dodasz do niej kilka nieszkodliwych metod, a następnie sprawdź, czy jakiś obiekt podklasy jest równy obiektowi superklasy, nawet jeśli obiekty są równe we wszystkich ważnych aspektach, otrzymasz zaskakująca odpowiedź, że nie są sobie równi. W rzeczywistości narusza to ścisłą interpretację zasady substytucji Liskova i może prowadzić do bardzo zaskakującego zachowania. W Javie jest to szczególnie ważne, ponieważ większość kolekcji (HashTableitp.) opierają się na metodzie równości. Jeśli umieścisz członka superklasy w tablicy skrótów jako klucz, a następnie sprawdzisz go za pomocą instancji podklasy, nie znajdziesz go, ponieważ nie są równe.

Zobacz także tę odpowiedź SO .

Efektywna Java rozdział 3 również to obejmuje.

Michael Myers
źródło
18
+1 Każdy, kto używa języka Java, powinien przeczytać tę książkę co najmniej 10 razy!
André
16
Problem z podejściem instanceof polega na tym, że łamie ono "symetryczną" właściwość Object.equals (), ponieważ staje się możliwe, że x.equals (y) == true, ale y.equals (x) == false, jeśli x.getClass ()! = y.getClass (). Wolę nie przerywać tej właściwości, chyba że jest to absolutnie konieczne (tj. Przesłanianie equals () dla obiektów zarządzanych lub proxy).
Kevin Sitze
8
Podejście instanceof jest właściwe wtedy i tylko wtedy, gdy klasa bazowa definiuje, co powinna oznaczać równość między obiektami podklas. Używanie getClassnie narusza LSP, ponieważ LSP odnosi się jedynie do tego, co można zrobić z istniejącymi instancjami, a nie do tego, jakiego rodzaju instancje można skonstruować. Klasa zwrócona przez getClassjest niezmienną właściwością instancji obiektu. LSP nie oznacza, że ​​powinno być możliwe utworzenie podklasy, w której ta właściwość wskazuje inną klasę niż ta, która ją utworzyła.
supercat
66

Angelika Langers Tajemnice równości wkraczają w to po długiej i szczegółowej dyskusji kilku popularnych i dobrze znanych przykładów, w tym autorstwa Josha Blocha i Barbary Liskov, odkrywając w większości z nich kilka problemów. Wstaje również Into the instanceofvs getClass. Jakiś cytat z tego

Wnioski

Po przeanalizowaniu czterech arbitralnie wybranych przykładów implementacji równości (), do czego dochodzimy?

Po pierwsze: istnieją dwa zasadniczo różne sposoby przeprowadzania sprawdzenia zgodności typu w implementacji equals (). Klasa może umożliwiać porównywanie typów mieszanych między obiektami super- i podklas za pomocą operatora instanceof lub klasa może traktować obiekty różnego typu jako nierówne za pomocą testu getClass (). Powyższe przykłady dobrze ilustrują, że implementacje equals () przy użyciu metody getClass () są generalnie bardziej niezawodne niż implementacje używające instancji instanceof.

Instancja testu jest poprawna tylko dla klas końcowych lub jeśli przynajmniej metoda equals () jest ostateczna w nadklasie. To ostatnie zasadniczo oznacza, że ​​żadna podklasa nie może rozszerzać stanu nadklasy, ale może dodawać tylko funkcje lub pola, które nie mają znaczenia dla stanu i zachowania obiektu, takie jak pola przejściowe lub statyczne.

Z drugiej strony implementacje używające testu getClass () zawsze są zgodne z kontraktem equals (); są poprawne i solidne. Są jednak semantycznie bardzo różne od implementacji, które używają testu instanceof. Implementacje używające getClass () nie pozwalają na porównywanie podklasy z obiektami nadklasy, nawet jeśli podklasa nie dodaje żadnych pól i nie chce nawet przesłonić równań (). Takim „trywialnym” rozszerzeniem klasy byłoby na przykład dodanie metody debug-print w podklasie zdefiniowanej dokładnie dla tego „trywialnego” celu. Jeśli nadklasa zabrania porównywania typów mieszanych za pomocą sprawdzenia getClass (), to trywialne rozszerzenie nie byłoby porównywalne z jego nadklasą. To, czy jest to problem, w pełni zależy od semantyki klasy i celu rozszerzenia.

Johannes Schaub - litb
źródło
61

Celem stosowania getClassjest zapewnienie symetryczności equalsumowy. Z równych dokumentów JavaDocs:

Jest symetryczny: dla wszelkich niezerowych wartości odniesienia xiy, x.equals (y) powinno zwracać prawdę wtedy i tylko wtedy, gdy y.equals (x) zwraca prawdę.

Używając instanceof, można nie być symetrycznym. Rozważmy przykład: Pies rozszerza Animal. Zwierzęcia equalsrobi się instanceofkontrolę zwierzęcia. Psa equalsrobi to instanceofsprawdzenie Dog. Daj Zwierzę a i Pies d (z innymi polami to samo):

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

Narusza to właściwość symetrii.

Aby ściśle przestrzegać kontraktu równego, należy zapewnić symetrię, a zatem klasa musi być taka sama.

Steve Kuo
źródło
8
To pierwsza jasna i zwięzła odpowiedź na to pytanie. Przykładowy kod jest wart tysiąca słów.
Johnride
Przeglądałem wszystkie inne wyczerpujące odpowiedzi, które uratowałeś dzień. Prosta, zwięzła, elegancka i absolutnie najlepsza odpowiedź na to pytanie.
1
Jak wspomniałeś, istnieje wymóg symetrii. Ale jest też wymóg „przechodniości”. Jeśli a.equals(c)i b.equals(c)wtedy a.equals(b)(naiwne podejście polegające na robieniu Dog.equalstylko return super.equals(object)wtedy, !(object instanceof Dog)ale sprawdzanie dodatkowych pól, gdy jest to instancja Dog, nie naruszyłoby symetrii, ale naruszyłoby przechodniość)
Shadow Man
Aby być bezpiecznym, możesz skorzystać z getClass()testu równości lub możesz użyć instanceofczeku, jeśli masz equalsi hashCodemetody final.
Shadow Man,
26

To coś w rodzaju debaty religijnej. Oba podejścia mają swoje problemy.

  • Użyj instanceof i nigdy nie możesz dodawać ważnych członków do podklas.
  • Użyj getClass i naruszysz zasadę zastępowania Liskova .

Bloch ma kolejną istotną radę w Effective Java Second Edition :

  • Punkt 17: Zaprojektuj i udokumentuj dziedziczenie lub zakazaj tego
McDowell
źródło
1
Nic w używaniu getClassnie naruszałoby LSP, chyba że klasa bazowa wyraźnie udokumentowała sposób, za pomocą którego powinno być możliwe uczynienie instancji różnych podklas, które porównują się równo. Jakie naruszenie LSP widzisz?
supercat
Być może powinno to zostać przeformułowane jako Użyj getClass i pozostawisz podtypy w niebezpieczeństwie naruszenia LSP. Bloch ma przykład w Efektywnej Javie - również tutaj omówiony . Jego uzasadnieniem jest głównie to, że może to spowodować zaskakujące zachowanie dla programistów implementujących podtypy, które nie dodają stanu. Rozumiem - dokumentacja jest kluczowa.
McDowell
2
Jedyny przypadek, w którym dwie instancje odrębnych klas powinny kiedykolwiek zgłosić się jako równe, to sytuacja, w której dziedziczą one ze wspólnej klasy bazowej lub interfejsu, który definiuje, co oznacza równość [filozofia, którą Java zastosowała, wątpliwie IMHO, do niektórych interfejsów kolekcji]. O ile umowa dotycząca klasy bazowej nie mówi, że getClass()nie powinno to być uważane za znaczące poza faktem, że dana klasa jest konwertowalna na klasę bazową, zwrot z getClass()powinien być traktowany jak każda inna właściwość, która musi być zgodna, aby wystąpienia były równe.
supercat
1
@eyalzba Gdzie instanceof_ jest używany, jeśli podklasa dodała członków do kontraktu equals, naruszyłoby to wymóg równości symetrycznej . Używanie podklas getClass nigdy nie może być równe typowi nadrzędnemu. Nie znaczy to, że i tak powinieneś nadpisywać równa się, ale to jest kompromis, jeśli to zrobisz.
McDowell
2
„Projektuj i dokumentuj w celu dziedziczenia lub zakazuj” Myślę, że jest to bardzo ważne. Rozumiem, że jedyną sytuacją, w której należy przesłonić równość, jest tworzenie niezmiennego typu wartości, w którym to przypadku klasa powinna zostać uznana za ostateczną. Ponieważ nie będzie dziedziczenia, możesz zaimplementować równe stosownie do potrzeb. W przypadkach, w których zezwalasz na dziedziczenie, obiekty powinny być porównywane według równości odwołań.
TheSecretSquad
23

Popraw mnie, jeśli się mylę, ale metoda getClass () przyda się, gdy chcesz się upewnić, że Twoja instancja NIE jest podklasą klasy, z którą porównujesz. Jeśli używasz w takiej sytuacji instanceof, NIE możesz tego wiedzieć, ponieważ:

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
TraderJoeChicago
źródło
Dla mnie to jest powód
karlihnos
5

Jeśli chcesz mieć pewność, że tylko ta klasa będzie pasować, użyj getClass() ==. Jeśli chcesz dopasować podklasy, instanceofjest to potrzebne.

Ponadto instanceof nie pasuje do wartości null, ale można ją bezpiecznie porównać z wartością null. Więc nie musisz tego sprawdzać.

if ( ! (obj instanceof MyClass) ) { return false; }
Clint
źródło
Do drugiej kwestii: wspomniał już o tym w pytaniu. „Zwykle sprawdzam opcję instanceof, a następnie wchodzę i usuwam zaznaczenie„ if (obj == null) ”.
Michael Myers
5

Zależy to od tego, czy weźmiesz pod uwagę, czy podklasa danej klasy jest równa swojej klasie macierzystej.

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

tutaj użyłbym „instanceof”, ponieważ chcę, aby LastName był porównywany z FamilyName

class Organism
{
}

class Gorilla extends Organism
{
}

tutaj użyłbym „getClass”, ponieważ klasa mówi już, że te dwa wystąpienia nie są równoważne.

Pierre
źródło
3

instanceof działa dla instancji tej samej klasy lub jej podklas

Można go użyć do sprawdzenia, czy obiekt jest instancją klasy, instancją podklasy lub instancją klasy implementującej określony interfejs.

ArryaList i RoleList są instancjami listy

Podczas

getClass () == o.getClass () będzie prawdziwe tylko wtedy, gdy oba obiekty (this i o) należą do dokładnie tej samej klasy.

Więc w zależności od tego, co chcesz porównać, możesz użyć jednego lub drugiego.

Jeśli Twoja logika brzmi: „Jeden obiekt jest równy drugiemu tylko wtedy, gdy oba należą do tej samej klasy”, powinieneś wybrać „równe”, co moim zdaniem jest w większości przypadków.

OscarRyz
źródło
3

Obie metody mają swoje problemy.

Jeśli podklasa zmieni tożsamość, musisz porównać ich rzeczywiste klasy. W przeciwnym razie naruszysz właściwość symetrii. Na przykład różne typy Personznaków nie powinny być uważane za równoważne, nawet jeśli mają taką samą nazwę.

Jednak niektóre podklasy nie zmieniają tożsamości i trzeba ich używać instanceof. Na przykład, jeśli mamy kilka niezmiennych Shapeobiektów, to a Rectangleo długości i szerokości 1 powinno być równe jednostce Square.

W praktyce myślę, że ten pierwszy przypadek jest bardziej prawdopodobny. Zwykle tworzenie podklas jest fundamentalną częścią twojej tożsamości, a bycie dokładnie takim jak twój rodzic, z wyjątkiem jednej małej rzeczy, nie czyni cię równym.

James
źródło
-1

Właściwie instancja sprawdzenia, czy obiekt należy do jakiejś hierarchii, czy nie. np .: obiekt samochodowy należy do klasy pojazdów. Zatem „new Car () instancja klasy Vehical” zwraca wartość true. A „new Car (). GetClass (). Equals (Vehical.class)” zwraca false, chociaż obiekt Car należy do klasy Vehical, ale jest sklasyfikowany jako oddzielny typ.

Akash5288
źródło