Siamo uguali?
Scritto da Michele Della Torre il 17 gennaio 2008 – 21:43Ci sono argomenti di ingegneria del software che inizialmente sembrano semplici, ma poi ragionandoci sopra ci si rende conto che non è proprio così: l’uguaglianza tra oggetti è uno di questi.
La domanda sembra banale: sotto quali condizioni un oggetto a è uguale ad un oggetto b?
In termini formali l’uguaglianza è una relazione riflessiva, transitiva e simmetrica; le specifiche di Java richiedono anche che sia consistente, cioè che il risultato dell’operazione rimanga lo stesso purchè nessuna informazione usata nella comparazione venga modificata: in altri termini due oggetti il cui stato non viene modificato devono essere sempre uguali o sempre diversi.
In Java, e in molti linguaggi OO, esiste una strettissima relazione tra l’equals e l’hash code: due oggetti uguali devono avere lo stesso hash code, inoltre quest’ultimo non deve cambiare se non ci sono cambiamenti nello stato.
Oggetti immutabili non pongono particolari problemi, mentre per quelli mutabili la situazione è più complicata (altrimenti questo post non avrebbe nemmeno molto senso ).
Fondalmentalmente esistono due scuole di pensiero: la prima dice che due oggetti devono essere sempre uguali o sempre diversi per tutta la durata dell’applicazione, la seconda invece permette variazioni nel risultato.
Nelle API di Java si adotta spesso il secondo approccio, ad esempio per la classe Calendar (che comunque non rimane un esempio di design da seguire…), ma questo crea problemi tutte le volte in cui si indicizza qualcosa tramite hash code, come nelle HashMap o HashSet e in generale nei Set, tant’è che nella javadoc si trova questa frase:
Note: Great care must be exercised if mutable objects are used as set elements. The behavior of a set is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is an element in the set.
Per evitare questi problemi si può seguire l’approccio opposto, cioè sostenere che se due oggetti sono uguali, allora lo sono per sempre e che se sono diversi allora lo sono per sempre. E’ possibile spingersi oltre e dire che deve valere l’uguaglianza comportamentale, cioè che due o più oggetti sono uguali allora non è possibile distinguerli richiamando in un ordine qualsiasi i loro metodi. Questo è l’approccio consigliato da Liskov.
La diretta conseguenza è piuttosto radicale: due oggetti mutabili sono uguali solo se sono lo stesso oggetto, quindi il metodo equals non deve essere ridefinito, così come il metodo hashCode.
In Refactoring Martin Fowler fa una classificazione decisamente interessante in questo senso: ci sono oggetti dei quali ci interessa il valore e oggetti di cui ci interessa l’identità. Fanno parte della prima categoria ad esempio le date e la valuta: in questo caso andrebbero implementati come oggetti immutabili con l’override di equals; appartengono alla seconda categoria ad esempio le persone dove il reale interesse è legato all’identità: istanze mutabili sono quindi legittime, ma potrebbe essere necessario introdurre una factory per garantire che l’esistenza di una singola istanza quando si richiede lo stesso oggetto in punti diversi del programma.
Inizialmente ero molto scettico riguardo a questa definizione di uguaglianza, ma poi ho iniziato a seguirla in modo metodico e devo ammettere che funziona molto bene. Non vi è una grande perdita di libertà nell’implementazione, ma se ne guadagna parecchio in chiarezza del design: ogni volta che mi trovo a creare una nuova classe mi chiedo se sono interessato al valore o all’identità, prendendo poi le decisioni del caso.
gennaio 23rd, 2008 at 23:35
Ciao Acce! anzi, Micheledellatorre.
Complimenti per il blog!
Bel post interessante su cui bisognerebbe discutere parecchio. Con più ci si addentra nell’argomento, con più la cosa si complica.
Approfitto per inserire due fonti molto interessanti se qualcuno fosse intenzionato ad approfondire un pochino la questione, specialmente nel mondo Java
Defining hashCode() and equals() effectively and correctly:
http://www.ibm.com/developerworks/java/library/j-jtp05273.html
Capitolo su equals() and hashcode() da “Effective Java Programming Language Guide”
http://developer.java.sun.com/developer/Books/effectivejava/Chapter3.pdf
Per quanto riguarda .NET invece bisognerebbe parlare dell’interfaccia IEquatable, ma quello sarà un altro bel post.
Ciao ciao!