Qualche motivo per preferire getClass () rispetto a instanceof quando si genera .equals ()?


174

Sto usando Eclipse per generare .equals()e .hashCode(), e c'è un'opzione etichettata "Usa 'instanceof' per confrontare i tipi". L'impostazione predefinita è che questa opzione sia deselezionata e utilizzata .getClass()per confrontare i tipi. C'è qualche motivo dovrei preferire .getClass()sopra instanceof?

Senza usare instanceof:

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

Utilizzando instanceof:

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

Di solito controllo l' instanceofopzione, quindi entro e rimuovo il if (obj == null)segno " ". (È ridondante poiché gli oggetti null falliranno sempre instanceof.) C'è qualche ragione che è una cattiva idea?


1
L'espressione x instanceof SomeClassè falsa se lo xè null. Pertanto, la seconda sintassi non necessita del controllo null.
RS,

5
Sì, lo dice anche il paragrafo immediatamente dopo lo snippet di codice. È nello snippet di codice perché è ciò che genera Eclipse.
Salta il

Risposte:


100

Se si utilizza instanceof, rendendo la vostra equalsimplementazione finalmanterrà il contratto di simmetria del metodo: x.equals(y) == y.equals(x). Se finalsembra restrittivo, esamina attentamente la tua nozione di equivalenza di oggetti per assicurarti che le tue implementazioni prioritarie mantengano pienamente il contratto stabilito dalla Objectclasse.


esattamente quello che mi è venuto in mente quando ho letto la citazione di Josh Bloch sopra. +1 :)
Johannes Schaub -

13
sicuramente la decisione chiave. la simmetria deve essere applicata, e instanceof rende molto facile essere asimmetrici per caso
Scott Stanchfield

Aggiungo anche che "se finalsembra restrittivo" (su entrambi equalse hashCode), allora uso l' getClass()uguaglianza invece di instanceofpreservare i requisiti di simmetria e transitività del equalscontratto.
Shadow Man,

176

Josh Bloch favorisce il tuo approccio:

Il motivo per cui preferisco l' instanceofapproccio è che quando si utilizza l' getClassapproccio, si ha la limitazione che gli oggetti sono uguali solo ad altri oggetti della stessa classe, lo stesso tipo di runtime. Se estendi una classe e aggiungi un paio di metodi innocui ad essa, quindi controlla se un oggetto della sottoclasse è uguale a un oggetto della superclasse, anche se gli oggetti sono uguali in tutti gli aspetti importanti, otterrai risposta sorprendente che non sono uguali. In realtà, ciò viola un'interpretazione rigorosa del principio di sostituzione di Liskov e può portare a comportamenti molto sorprendenti. In Java, è particolarmente importante perché la maggior parte delle raccolte (HashTable, ecc.) si basano sul metodo uguale. Se inserisci un membro della superclasse in una tabella hash come chiave e poi lo cerchi usando un'istanza di sottoclasse, non la troverai, perché non sono uguali.

Vedi anche questa risposta SO .

L'efficace capitolo 3 di Java tratta anche questo.


18
+1 Tutti quelli che fanno java dovrebbero leggere quel libro almeno 10 volte!
André,

16
Il problema con l'approccio instanceof è che rompe la proprietà "simmetrica" ​​di Object.equals () in quanto diventa possibile che x.equals (y) == true ma y.equals (x) == false se x.getClass ()! = y.getClass (). Preferisco non rompere questa proprietà a meno che non sia assolutamente necessario (ovvero, sostituire uguale a () per oggetti gestiti o proxy).
Kevin Sitze,

8
L'approccio dell'istanza è corretto quando, e solo quando, la classe base definisce cosa dovrebbe significare l'uguaglianza tra gli oggetti delle sottoclassi. L'uso getClassnon viola l'LSP, poiché l'LSP si riferisce semplicemente a cosa si può fare con le istanze esistenti, non a quali tipi di istanze possono essere costruite. La classe restituita da getClassè una proprietà immutabile di un'istanza di oggetto. L'LSP non implica che dovrebbe essere possibile creare una sottoclasse in cui tale proprietà indica una classe diversa da quella che l'ha creata.
supercat

66

Angelika Langers Segreti di uguali si occupa di questo con una lunga e dettagliata discussione per alcuni esempi comuni e ben noti, tra cui Josh Bloch e Barbara Liskov, scoprendo un paio di problemi nella maggior parte di essi. Si ottiene anche nel instanceofvs getClass. Qualche citazione da esso

conclusioni

Dopo aver analizzato i quattro esempi scelti in modo arbitrario di implementazioni di uguali (), cosa concludiamo?

Prima di tutto: ci sono due modi sostanzialmente diversi di eseguire il controllo per la corrispondenza del tipo in un'implementazione di equals (). Una classe può consentire il confronto di tipo misto tra oggetti super e sottoclasse mediante l'operatore instanceof, oppure una classe può trattare oggetti di tipo diverso come non uguali mediante il test getClass (). Gli esempi sopra illustrati mostrano bene che le implementazioni di equals () usando getClass () sono generalmente più solide di quelle implementazioni che usano instanceof.

Il test di instanceof è corretto solo per le classi finali o se almeno il metodo equals () è definitivo in una superclasse. Quest'ultimo implica essenzialmente che nessuna sottoclasse deve estendere lo stato della superclasse, ma può solo aggiungere funzionalità o campi che sono irrilevanti per lo stato e il comportamento dell'oggetto, come campi transitori o statici.

Le implementazioni che utilizzano il test getClass () invece sono sempre conformi al contratto equals (); sono corretti e robusti. Sono, tuttavia, semanticamente molto diversi dalle implementazioni che utilizzano il test instanceof. Le implementazioni che utilizzano getClass () non consentono il confronto di sottoclassi con oggetti superclasse, nemmeno quando la sottoclasse non aggiunge campi e non vorrebbe nemmeno sovrascrivere equals (). Una tale estensione di classe "banale" sarebbe ad esempio l'aggiunta di un metodo di debug-print in una sottoclasse definita esattamente per questo scopo "banale". Se la superclasse proibisce il confronto di tipo misto tramite il controllo getClass (), l'estensione banale non sarebbe paragonabile alla sua superclasse. Se questo è un problema dipende completamente dalla semantica della classe e dallo scopo dell'estensione.


61

Il motivo da utilizzare getClassè garantire la proprietà simmetrica del equalscontratto. Da JavaDocs uguale a:

È simmetrico: per qualsiasi valore di riferimento non nullo x e y, x.equals (y) dovrebbe restituire true se e solo se y.equals (x) restituisce true.

Usando instanceof, è possibile non essere simmetrici. Considera l'esempio: Dog extends Animal. L'animale equalsfa un instanceofcontrollo di Animal. Il cane equalsfa un instanceofcontrollo del cane. Dai a Animal a e Dog d (con altri campi uguali):

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

Ciò viola la proprietà simmetrica.

Per seguire rigorosamente il contratto di parità, è necessario garantire la simmetria e quindi la classe deve essere la stessa.


8
Questa è la prima risposta chiara e concisa per questa domanda. Un esempio di codice vale più di mille parole.
Johnride,

Stavo esaminando tutte le altre risposte verbose che hai salvato la giornata. Semplice, concisa, elegante e assolutamente la migliore risposta a questa domanda.

1
C'è il requisito di simmetria come hai menzionato. Ma esiste anche il requisito della "transitività". If a.equals(c)and b.equals(c), then a.equals(b)(l'approccio ingenuo di fare Dog.equalsproprio return super.equals(object)quando, !(object instanceof Dog)ma controllando i campi extra quando si tratta di un'istanza Dog, non violerebbe la simmetria ma violerebbe la transitività)
Shadow Man

Per sicurezza, potresti usare un getClass()controllo di uguaglianza, oppure puoi usare un instanceofcontrollo se fai il tuo equalse hashCodemetodi final.
Shadow Man,

26

Questa è una specie di dibattito religioso. Entrambi gli approcci hanno i loro problemi.

  • Usa instanceof e non puoi mai aggiungere membri significativi alle sottoclassi.
  • Utilizzare getClass e si viola il principio di sostituzione di Liskov.

Bloch ha un altro importante consiglio in Effective Java Second Edition :

  • Articolo 17: progettare e documentare l'eredità o vietarlo

1
Nulla sull'uso getClassviolerebbe l'LSP, a meno che la classe base non abbia documentato in modo specifico un mezzo con cui dovrebbe essere possibile rendere uguali le istanze di sottoclassi diverse. Quale violazione LSP vedi?
supercat

Forse questo dovrebbe essere riformulato come Usa getClass e lascerai i sottotipi in pericolo di violazioni di LSP. Bloch ha un esempio in Effective Java - anche discusso qui . La sua logica è in gran parte che può provocare comportamenti sorprendenti per gli sviluppatori che implementano sottotipi che non aggiungono stato. Prendo il tuo punto: la documentazione è la chiave.
McDowell,

2
L'unica volta in cui due istanze di classi distinte dovrebbero mai riferirsi è uguale è se ereditano da una classe o interfaccia di base comune che definisce cosa significa uguaglianza [una filosofia che Java ha applicato, senza dubbio IMHO, ad alcune interfacce di raccolta]. A meno che il contratto della classe base non dica che getClass()non dovrebbe essere considerato significativo oltre al fatto che la classe in questione è convertibile nella classe base, il ritorno da getClass()dovrebbe essere considerato come qualsiasi altra proprietà che deve corrispondere affinché le istanze siano uguali.
supercat

1
@eyalzba Laddove instanceof_ viene utilizzato se una sottoclasse ha aggiunto membri al contratto uguale, ciò violerebbe il requisito di uguaglianza simmetrica . L'uso delle sottoclassi getClass non può mai essere uguale al tipo parent. Non che dovresti sovrascrivere uguali comunque, ma questo è il compromesso se lo fai.
McDowell,

2
"Progettare e documentare l'ereditarietà o vietarlo" Penso che sia molto importante. La mia comprensione è che l'unica volta in cui dovresti sovrascrivere è uguale se stai creando un tipo di valore immutabile, nel qual caso la classe dovrebbe essere dichiarata finale. Dal momento che non ci sarà eredità, sei libero di implementare gli uguali secondo necessità. Nei casi in cui si consente l'ereditarietà, gli oggetti devono essere confrontati per uguaglianza di riferimento.
TheSecretSquad

23

Correggimi se sbaglio, ma getClass () sarà utile quando vuoi assicurarti che la tua istanza NON sia una sottoclasse della classe con cui stai confrontando. Se usi istanza in quella situazione NON puoi saperlo perché:

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

Per me questo è il motivo
karlihnos il

5

Se vuoi assicurarti che solo quella classe corrisponderà, usa getClass() ==. Se si desidera abbinare le sottoclassi, instanceofè necessario.

Inoltre, instanceof non corrisponderà a un null, ma è sicuro da confrontare con un null. Quindi non devi null controllarlo.

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

Per quanto riguarda il tuo secondo punto: lo ha già menzionato nella domanda. "Di solito controllo l'opzione instanceof, quindi entro e rimuovo il controllo 'if (obj == null)'."
Michael Myers

5

Dipende se si considera se una sottoclasse di una determinata classe è uguale al suo genitore.

class LastName
{
(...)
}


class FamilyName
extends LastName
{
(..)
}

qui userei 'instanceof', perché voglio un LastName da confrontare con FamilyName

class Organism
{
}

class Gorilla extends Organism
{
}

qui userei 'getClass', perché la classe dice già che le due istanze non sono equivalenti.


3

instanceof funziona per istanze della stessa classe o delle sue sottoclassi

Puoi usarlo per verificare se un oggetto è un'istanza di una classe, un'istanza di una sottoclasse o un'istanza di una classe che implementa una particolare interfaccia.

ArryaList e RoleList sono entrambi elenco di esempio

Mentre

getClass () == o.getClass () sarà vero solo se entrambi gli oggetti (this e o) appartengono esattamente alla stessa classe.

Quindi, a seconda di cosa devi confrontare, potresti usare l'uno o l'altro.

Se la tua logica è: "Un oggetto è uguale all'altro solo se sono entrambi della stessa classe", dovresti optare per "uguale", che penso sia nella maggior parte dei casi.


3

Entrambi i metodi hanno i loro problemi.

Se la sottoclasse modifica l'identità, è necessario confrontare le loro classi effettive. Altrimenti, si viola la proprietà simmetrica. Ad esempio, diversi tipi di Persons non devono essere considerati equivalenti, anche se hanno lo stesso nome.

Tuttavia, alcune sottoclassi non cambiano identità e queste devono essere utilizzate instanceof. Ad esempio, se abbiamo un mucchio di Shapeoggetti immutabili , allora un Rectanglecon lunghezza e larghezza di 1 dovrebbe essere uguale all'unità Square.

In pratica, penso che il primo caso abbia maggiori probabilità di essere vero. Di solito, la sottoclasse è una parte fondamentale della tua identità ed essere esattamente come il tuo genitore, tranne che puoi fare una piccola cosa non ti rende uguale.


-1

In realtà, verifica se un oggetto appartiene o meno a una gerarchia. es: l'oggetto auto appartiene alla classe Vehical. Quindi "nuova istanza Car () di Vehical" restituisce true. E "new Car (). GetClass (). Equals (Vehical.class)" restituisce false, sebbene l'oggetto Car appartiene alla classe Vehical ma è classificato come un tipo separato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.