Qualsiasi motivo per preferire getClass () su instanceof durante la generazione di .equals ()?

Sto usando Eclipse per generare .equals() e .hashCode() , e c’è un’opzione etichettata “Use” instanceof “per confrontare i tipi”. L’impostazione predefinita è deselezionare questa opzione e utilizzare .getClass() per confrontare i tipi. C’è qualche ragione per cui preferire .getClass() su 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’opzione instanceof , quindi vado a rimuovere il controllo ” if (obj == null) “. (È ridondante poiché gli oggetti nulli falliranno sempre l’ instanceof .) C’è qualche ragione che sia una ctriggers idea?

Se si utilizza instanceof , la final dell’implementazione equals a preservare il contratto di simmetria del metodo: x.equals(y) == y.equals(x) . Se la final sembra restrittiva, esamina attentamente la tua nozione di equivalenza degli oggetti per assicurarti che le tue implementazioni prevalenti mantengano pienamente il contratto stabilito dalla class Object .

Josh Bloch favorisce il tuo approccio:

Il motivo per cui preferisco l’approccio instanceof è che quando si utilizza l’approccio getClass , si ha la restrizione che gli oggetti siano uguali ad altri oggetti della stessa class, lo stesso tipo di runtime. Se estendete una class e aggiungete un paio di metodi innocui, controllate se alcuni oggetti della sottoclass sono uguali a un object della super class, anche se gli oggetti sono uguali in tutti gli aspetti importanti, otterrete il risposta sorprendente che non sono uguali. In realtà, questo viola una rigorosa interpretazione del principio di sostituzione di Liskov e può portare a comportamenti molto sorprendenti. In Java, è particolarmente importante perché la maggior parte delle collezioni ( HashTable , ecc.) Sono basate sul metodo equals. Se metti un membro della super class in una tabella hash come chiave e poi lo cerchi usando un’istanza di sottoclass, non lo troverai, perché non sono uguali.

Vedi anche questa risposta SO .

L’efficace capitolo 3 di Java copre anche questo.

Angelika Langers Segreti di eguali si inseriscono in questo con una discussione lunga e dettagliata per alcuni esempi comuni e ben noti, tra cui Josh Bloch e Barbara Liskov, scoprendo un paio di problemi nella maggior parte di essi. Entra anche in instanceof vs getClass . Alcuni ne citano

conclusioni

Dopo aver sezionato i quattro esempi scelti arbitrariamente di implementazioni di equals (), che 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 class può consentire il confronto di tipo misto tra oggetti super e di sottoclassi mediante l’operatore instanceof, oppure una class può trattare oggetti di tipo diverso come non uguali mediante il test getClass (). Gli esempi sopra illustrati descrivono bene che le implementazioni di equals () usando getClass () sono generalmente più robuste di quelle che usano instanceof.

L’instanceof test è corretta solo per le classi finali o se almeno il metodo equals () è finale in una superclass. Quest’ultima implica essenzialmente che nessuna sottoclass deve estendere lo stato della superclass, ma può solo aggiungere funzionalità o campi che sono irrilevanti per lo stato e il comportamento dell’object, come i campi transitori o statici.

Le implementazioni che utilizzano il test getClass () d’altra parte sono sempre conformi al contratto equals (); sono corretti e robusti. Tuttavia, sono semanticamente molto diversi dalle implementazioni che utilizzano l’instanceof test. Le implementazioni che utilizzano getClass () non consentono il confronto degli oggetti sub- con superclass, nemmeno quando la sottoclass non aggiunge campi e non vorrebbe nemmeno sovrascrivere equals (). Un’estensione di class così “banale” sarebbe ad esempio l’aggiunta di un metodo di debug-print in una sottoclass definita proprio per questo scopo “banale”. Se la superclass proibisce il confronto di tipo misto tramite il controllo getClass (), l’estensione banale non sarebbe paragonabile alla sua superclass. Che questo sia o meno un problema dipende completamente dalla semantica della class e dallo scopo dell’estensione.

La ragione per utilizzare getClass è garantire la proprietà simmetrica del contratto di equals . 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, è ansible non essere simmetrici. Considera l’esempio: Dog extends Animal. L’animale è equals instanceof controllo dell’animale. Il cane è equals instanceof controllo del cane. Dai a Animal a and Dog d (con altri campi uguali):

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

Questo viola la proprietà simmetrica.

Per seguire rigorosamente il contratto di uguale, bisogna garantire la simmetria, e quindi la class deve essere la stessa.

Questo è qualcosa di un dibattito religioso. Entrambi gli approcci hanno i loro problemi.

  • Utilizza instanceof e non puoi mai aggiungere membri significativi a sottoclassi.
  • Usa getClass e viola il principio di sostituzione di Liskov.

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

  • Articolo 17: Progettare e documentare per ereditarietà o vietarlo

Correggimi se sbaglio, ma getClass () sarà utile quando vuoi assicurarti che la tua istanza NON sia una sottoclass della class con cui stai confrontando. Se usi instanceof 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 

Dipende se si considera se una sottoclass di una data class è uguale al suo genitore.

 class LastName { (...) } class FamilyName extends LastName { (..) } 

qui userei ‘instanceof’, perché voglio che un LastName sia paragonato a FamilyName

 class Organism { } class Gorilla extends Organism { } 

qui userei ‘getClass’, perché la class dice già che le due istanze non sono equivalenti.

Se vuoi assicurarti che solo quella class corrisponda, usa getClass() == . Se vuoi abbinare le sottoclassi, allora è necessario instanceof .

Inoltre, instanceof non corrisponderà a un valore nullo ma è sicuro confrontarlo con un valore nullo. Quindi non devi nullarlo.

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

instanceof funziona per istanze della stessa class o delle sue sottoclassi

Puoi usarlo per verificare se un object è un’istanza di una class, un’istanza di una sottoclass o un’istanza di una class che implementa un’interfaccia particolare.

ArryaList e RoleList sono entrambi instanceof List

Mentre

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

Quindi, a seconda di cosa devi confrontare, potresti usare l’una o l’altra.

Se la tua logica è: “Un object è uguale all’altro solo se sono entrambi della stessa class” dovresti andare per gli “uguali”, che credo sia la maggior parte dei casi.

Entrambi i metodi hanno i loro problemi.

Se la sottoclass modifica l’identity framework, è necessario confrontare le loro classi effettive. Altrimenti, si viola la proprietà simmetrica. Ad esempio, diversi tipi di Person non dovrebbero essere considerati equivalenti, anche se hanno lo stesso nome.

Tuttavia, alcune sottoclassi non cambiano id quadro e hanno bisogno di usare instanceof . Per esempio, se abbiamo un mucchio di oggetti Shape immutabili, allora un Rectangle con lunghezza e larghezza di 1 dovrebbe essere uguale al Square unitario.

In pratica, penso che il primo caso sia più probabile che sia vero. Di solito, la sottoclass è una parte fondamentale della tua id quadro ed essere esattamente come tuo genitore tranne che puoi fare una piccola cosa non ti rende uguale.

In realtà instanceof controlla dove un object appartiene ad una gerarchia o meno. ex: l’object Car appartiene alla class Vehical. Quindi “nuova istanza Car () di Vehical” restituisce true. E “new Car (). GetClass (). Equals (Vehical.class)” restituisce false, sebbene l’object Car appartenga alla class Vehical ma è classificato come un tipo separato.