Nel linguaggio Java, l’operatore == stabilisce se due riferimenti puntano al medesimo oggetto.
Spesso, è utile considerare uguali due oggetti distinti, secondo un criterio dettato di volta in volta dal contesto applicativo.
Il modo standard di confrontare oggetti è tramite il metodo equals, definito dalla classe Object.
public boolean equals(Object x)
In generale, il metodo equals prende come argomento un oggetto x e restituisce vero se questo oggetto (this) è da considerarsi “uguale” ad x, e falso altrimenti.
Nella classe Object, il metodo equals non fa altro che confrontare gli indirizzi di this e di x con ==
Ad esempio, consideriamo un programma di gestione del personale.
class Employee {
private String name;
private int salary;
private Employee boss;
...
}
Una possibile implementazione del metodo equals per la classe Employee è la seguente
public boolean equals(Object o) {
if (o == null) return false; // questo controllo è ridondante; perché?
if (!(o instanceof Employee)) return false;
Employee e = (Employee) o;
if ( name.equals(e.name) &&
(boss==e.boss || (boss!=null && boss.equals(e.boss))) ) return true;
return false;
}
All’inizio, controlliamo che il riferimento passato punti effettivamente ad un Employee.
Confrontiamo i nomi con equals.
Confrontiamo anche i capiufficio con equals (sono impiegati anche loro, quindi vale per loro la stessa assunzione fatta per gli impiegati semplici).
La forma complessa dell’espressione condizionale è dovuta al caso in cui uno dei due impiegati in questione, o entrambi, siano al vertice dell’azienda.
Il linguaggio Java richiede che qualunque ridefinizione del metodo equals rispetti le seguenti proprietà:
Queste sono le condizioni proprie di una relazione di equivalenza.
Bisogna prestare attenzione all’interazione tra il metodo equals e le sottoclassi.
Ad esempio, supponiamo che la classe Employee abbia una sottoclasse Manager, che a sua volta ha una sottoclasse Executive.
In base al contesto applicativo, in fase di progettazione, va deciso come il metodo equals si deve comportare con oggetti appartenenti a sottoclassi diverse.
In particolare, bisogna porsi le seguenti domande:
Consideriamo la prima domanda:
1) Il criterio di confronto tra oggetti di una sottoclasse (ad es., due Manager) è diverso da quello che vale tra oggetti della superclasse (ad es., due Employee)?
Se la risposta è “no”, il metodo equals, ridefinito in Employee, dovrebbe essere dichiarato “final”, in modo da impedire ulteriori ridefinizioni.
Se la risposta è “si”, il metodo equals va opportunamente ridefinito in ogni sottoclasse che abbia un proprio criterio di uguaglianza.
Il metodo equals nelle sottoclassi (ad es., Manager) dovrebbe preliminarmente chiamare quello della superclasse:
if (!super.equals(o)) return false;
Poi, se l’oggetto ricevuto come argomento è anch’esso un Manager, dovrebbe procedere ad effettuare i confronti specifici dei Manager.
Consideriamo la seconda domanda:
2) Un oggetto di una sottoclasse (es., Manager) può essere considerato uguale ad uno di una superclasse (es. Employee)?
Se la risposta è “no”, allora il metodo equals della classe Employee dovrebbe effettuare un controllo del tipo:
if (o.getClass() != Employee.class) return false
in modo da ammettere al confronto solo oggetti di tipo Employee;
allo stesso modo, se equals viene ridefinito anche in Manager (vedi domanda 1), in quella sede deve controllare che l’oggetto ricevuto come argomento sia a sua volta un Manager.
Se la risposta è “si”, allora il metodo equals della classe Employee dovrebbe effettuare un controllo del tipo:
if (!(o instanceof Employee)) return false
in modo da accettare anche oggetti di tipo Manager o Executive;
tuttavia, questo può portare a problemi di simmetria, come illustrato nella prossima slide.
Supponiamo che le risposte alle due domande siano “si” e “si”.
Concretamente, gli Employee sono uguali se hanno stesso nome e salario, mentre i Manager devono avere anche lo stesso “bonus” (un nuovo campo di tipo intero).
Un Employee è uguale ad un Manager se ha il suo stesso nome e salario.
Il metodo equals di Employee effettua un controllo “instanceof Employee” e poi confronta nomi e salari.
Per il metodo equals di Manager, è facile incorrere nel seguente errore:
public boolean equals(Object o) {
if (!(o instanceof Manager)) return false;
if (!(super.equals(o))) return false;
Manager m = (Manager)o;
return bonus==m.bonus;
}
Pertanto, la struttura corretta per equals in Manager è la seguente
public boolean equals(Object o) {
if (!(o instanceof Employee)) return false;
if (!(super.equals(o))) return false;
if (!(o instanceof Manager)) return true;
Manager m = (Manager)o;
return bonus==m.bonus;
}
Perché non è opportuno specializzare il tipo del parametro di equals?
In altre parole, perché è meglio effettuare l’overriding, piuttosto che l’overloading?
In generale, i metodi dovrebbero avere la firma più permissiva possibile, compatibilmente con il servizio che devono offrire.
In questo caso, equals di Employee deve accettare un altro oggetto e stabilire se tale oggetto è da considerarsi uguale a “questo” Employee.
Indubbiamente, la domanda non ha senso se il tipo effettivo dell’altro oggetto non è Employee (o al limite una sua sottoclasse).
Tuttavia, la domanda ha senso anche se il tipo dichiarato dell’altro oggetto non è Employee, ma ad esempio Object.
Per questo motivo, il metodo equals non dovrebbe precludere ai client la possibilità di passare riferimenti che siano di tipo dichiarato Object.
Il metodo equals interagisce con diverse funzionalità offerte dalla libreria standard Java.
In particolare:
Queste interazioni saranno trattate nelle lezioni successive.
Data la seguente classe.
public class Z {
private Z other;
private int val;
...
}
Si considerino le seguenti specifiche alternative per il metodo equals.
Due oggetti x e y di tipo Z sono uguali se:
a) x.other e y.other puntano allo stesso oggetto ed x.val è maggiore o uguale di y.val;
b) x.other e y.other puntano allo stesso oggetto ed x.val e y.val sono entrambi pari;
c) x.other e y.other puntano allo stesso oggetto oppure x.val è uguale a y.val;
d) x.other e y.other sono entrambi null oppure nessuno dei due è null ed x.other.val è uguale a y.other.val.
Dire quali specifiche sono valide e perché.
Implementare la specifica (d).
4. Risoluzione dell'overloading e dell'overriding
5. Controllo di uguaglianza tra oggetti
6. Classi interne, locali ed anonime
7. Iteratori, teoria e pratica
8. Clonazione di oggetti. Confronto tra oggetti.
9. Elementi di programmazione di interfacce grafiche
10. Il paradigma Model-View-Controller. Il pattern Strategy
11. I pattern Composite e Decorator
12. I pattern Template Method e Factory Method
13. Classi e metodi parametrici
14. La libreria Java Collection Framework: le interfacce Iterable, ...
15. La libreria Java Collection Framework: la classe HashSet e le l...
16. Parametri di tipo con limiti
17. L'implementazione della programmazione generica: la cancellazio...
18. La riflessione
19. Introduzione al multi-threading
22. Classi enumerate