Le classi Persona, Studente e Professore

Diagramma di classi:

Si osservi come, grazie all'ereditarietà, le informazioni comuni delle classi Studente e Professore sono fattorizzate nella classe Persona, da cui Studente e Professore ereditano. 


Gerarchia di classi

 
La definizione di sottoclassi può essere iterata a piacere: si possono definire delle gerarchie di classi arbitrariamente complesse.

In Java ogni classe è una classe derivata, in modo diretto o indiretto, dalla classe Object. Infatti, ciascuna classe che non estende un'altra classe estende automaticamente la classe Object.

 


 
 
 
 
 
 
 



La classe Object


La classe Object è la superclasse, diretta o indiretta, di ciascuna classe in Java.
Grazie al meccanismo dell'ereditarietà, i suoi metodi possono essere invocati su tutti gli oggetti.

Descrizione completa della classe Object: [locale, Medialab, Sun]

In particolare, alcuni metodi molto utili sono:

  • public String toString()
    Restituisce una rappresentazione testuale dell'oggetto in forma di stringa: è molto utile ad esempio per le stampe.

  • public boolean equals(Object obj)
    Verifica se l'oggetto su cui è invocato è uguale (equivalente) a quello passato per argomento.



Il metodo toString


Il metodo toString() restituisce una stringa che può essere considerata come la "rappresentazione testuale" dell'oggetto su cui è invocato (da usare ad esempio nella stampa). 

La definizione default del metodo nella classe Object restituisce una stringa del tipo 

    <classe>@<hashcode>
dove <classe> è il nome della classe dell'oggetto su cui il metodo è invocato, e <hashcode> è la rappresentazione esadecimale del codice hash dell'oggetto (indirizzo in memoria dell'oggetto). Questo accade perché la classe Object non può conoscere la struttura dell'oggetto.

Il metodo deve quindi essere ridefinito in ogni classe che lo usa, per ottenere un risultato significativo. Tipicamente, di un oggetto si vogliono stampare i valori delle variabili d'istanza, ed eventualmente una intestazione. 

Grazie all'esistenza del metodo toString(), in Java possiamo liberamente mettere una variabile di tipo riferimento in un contesto in cui ci dovrebbe essere una stringa (ad esempio in un comando di stampa, oppure come argomento dell'operatore di concatenazione +): l'oggetto verrà convertito in una stringa automaticamente invocando su di esso il metodo toString().


Esempi:

Il codice:

        Rectangle box = new Rectangle(5,10,20,30);
        System.out.println(box); 

è compilato correttamente. Il compilatore invoca automaticamente il metodo toString() su box, perché sa che ciascun oggetto ha questo metodo, dal momento che ogni classe estende la classe Object, che a sua volta definisce toString().


Si provi ad eseguire la classe PrintBankAccount che stampa un oggetto della classe BankAccount.
Poi si compili BankAccount1 (che aggiunge il metodo toString() riportato sotto), e si esegua PrintBankAccount1.

    // adds toString() to BankAccount

    public String toString(){
        return "Conto bancario con saldo: " + balance;
    }




Il metodo equals


Come descritto nella documentazione della classe Object: [locale, Medialab, Sun], il metodo equals deve implementare una relazione d'equivalenza sulle istanze di ogni classe, cioè una relazione riflessiva, simmetrica e transitiva (e deve soddisfare alcune altre proprietà).

Nella pratica, il metodo equals di una classe viene scritto in modo che l'invocazione <obj1>.equals(<obj2>) restituisce true quando gli oggetti <obj1> e <obj2> sono istanze della stessa classe e hanno uguale contenuto, cioè valori equivalenti per ogni variabile d'istanza.

Ad esempio, come abbiamo visto, due oggetti della classe String sono equals se sono costituiti dalla stessa sequenza di caratteri.

Nella classe Object, poichè non si può fare alcuna assunzione sulla struttura interna degli oggetti su cui viene invocato, il metodo equals è realizzato nel modo più restrittivo possibile: due oggetti sono considerati equivalenti solo se sono lo stesso oggetto. Quindi il confronto è basato sull'operatore ==.

Poichè tutte le classi ereditano da Object, il metodo equals può essere invocato su una istanza di una qualunque classe. Se però nella classe in questione il metodo non è stato sovrascritto, verrà eseguito il metodo ereditato da Object, con risultati che potrebbero essere inattesi.

Quindi il metodo equals deve essere ridefinito in tutte le classi in cui è necessario effettuare confronti, per ottenere risultati significativi.

Osservazione:
Quando in una classe si sovrascrive il metodo equals, la firma del metodo NON può essere modificata. Il parametro del metodo deve essere necessariamente un Object. Nel corpo del metodo si dovrà quindi eseguire un cast sul parametro per convertirlo nel tipo della classe.




Esempio


Estendiamo le classi Persona, Studente e Professore sovrascrivendo il metodo equals della classe Object.

public class Persona { 
    
    ...
    
    public boolean equals(Object obj) { 
        if (obj == nullreturn false;

        if (!(obj instanceof Persona)) return false;

        Persona p = (Persona) obj; 
        return ( this.omonimo(p) && 
                 this.indirizzo.equalsIgnoreCase(p.indirizzo) ); 
    } 
}
public class Studente extends Persona { 
    
    ...
    
    // Metodo sovrascritto
    public boolean equals(Object obj) { 
        if (!super.equals(obj)) return false; 

        if (!(obj instanceof Studente)) return false;
 
        Studente s = (Studente) obj; 
        return ( this.pianoDiStudio.equalsIgnoreCase(s.pianoDiStudio)
              && this.matricola == s.matricola ); 
    }  
}

public class Professore extends Persona { 
    
    ...
    
    public boolean equals(Object obj) { 
        if (!super.equals(obj)) return false;

        if (!(obj instanceof Professore)) return false;

        Professore p = (Professore) obj; 
        return ( this.ruolo.equalsIgnoreCase(p.ruolo)
              && this.stipendio == p.stipendio 
              && this.corsiAffidati.equalsIgnoreCase(p.corsiAffidati) ); 
    }  
}