Vai alla Home Page About me Courseware Federica Living Library Federica Federica Podstudio Virtual Campus 3D Le Miniguide all'orientamento Gli eBook di Federica La Corte in Rete
 
I corsi di Scienze Matematiche Fisiche e Naturali
 
Il Corso Le lezioni del Corso La Cattedra
 
Materiali di approfondimento Risorse Web Il Podcast di questa lezione

Marco Faella » 5.Controllo di uguaglianza tra oggetti


Uguaglianza tra oggetti

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 ==

Implementazione tipica

Ad esempio, consideriamo un programma di gestione del personale.

  • La classe Employee rappresenta un impiegato ed è caratterizzata da nome (stringa), salario mensile (numero intero) e capoufficio (un altro impiegato).
  • Lo schema della classe è il seguente:

class Employee {

private String name;
private int salary;
private Employee boss;
...

}

  • Supponiamo che due impiegati vadano considerati uguali se hanno lo stesso nome e lo stesso capoufficio
    • ovvero, l’applicazione suppone che non ci possano essere due impiegati omonimi all’interno dello stesso ufficio.
  • Supponiamo che la relazione “essere capoufficio di” disponga gli impiegati in un albero, alla cui radice vi è un impiegato senza capoufficio (boss==null).

Implementazione tipica (segue)

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.

Proprietà di equals

Il linguaggio Java richiede che qualunque ridefinizione del metodo equals rispetti le seguenti proprietà:

  • riflessività: per ogni oggetto x, x.equals(x) è vero;
  • simmetria: dati due oggetti x ed y, se x.equals(y) è vero se e solo se y.equals(x) è vero;
  • transitività: dati tre oggetti x, y e z, se x.equals(y) è vero e y.equals(z) è vero, allora anche x.equals(z) è vero;

Queste sono le condizioni proprie di una relazione di equivalenza.

Metodo equals ed ereditarietà

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:

  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)?
  2. Un oggetto di una sottoclasse (es., Manager) può essere considerato uguale ad uno di una superclasse (es. Employee)?

Metodo equals ed ereditarietà (segue)

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.

Metodo equals ed ereditarietà (segue)

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.

Problemi di simmetria

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;

}

  • Questa implementazione non rispetta le specifiche date in questa slide.
  • In particolare, questa implementazione non rispetta la proprietà simmetrica:
    • dati un Employee “e” ed un Manager “m”, con lo stesso nome e salario, risulterebbe e.equals(m) vero mentre m.equals(e) falso.

Problemi di simmetria (segue)

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é equals (Object)?

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.

Altre interazioni

Il metodo equals interagisce con diverse funzionalità offerte dalla libreria standard Java.
In particolare:

  • con il metodo hashCode di Object;
  • con le collezioni offerte dalla libreria Java Collection Framework.

Queste interazioni saranno trattate nelle lezioni successive.

Esercizio (esame 9/7/2008)

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).

  • Contenuti protetti da Creative Commons
  • Feed RSS
  • Condividi su FriendFeed
  • Condividi su Facebook
  • Segnala su Twitter
  • Condividi su LinkedIn
Progetto "Campus Virtuale" dell'Università degli Studi di Napoli Federico II, realizzato con il cofinanziamento dell'Unione europea. Asse V - Società dell'informazione - Obiettivo Operativo 5.1 e-Government ed e-Inclusion