ereditarieta

oggetti che ampliano o specializzano oggetti preesistenti

Le persone, gli studenti e i professori

Una realtà che ci è familiare è quella scolastica. Vogliamo rappresentare le persone che hanno a che fare con la scuola tramite la classe Persona. Queste persone possono essere i commessi del bar, il personale negli uffici, quelli che fanno il servizio al corridoio, il dirigente e così via. Di queste persone ci interessa il nome, il cognome, il ruolo svolto, lo stipendio mensile, la categoria (0: indefinito, 1 dirigente, 2: docente, 3: tecnico, 4: amministrativo, 5: collaboratore). La classe, con relativi costruttori, sarà una cosa del tipo:

public class Persona {
   public String categorie[] = {"indefinito (?)", "dirigente", "docente", "tecnico", "amministrativo", "collaboratore"};
   public String nome, cognome;
   public String ruolo;
   public float stipendio;
   public byte categoria;

   public Persona() {
      nome = cognome = "<indefinito>";
      ruolo = "<non assegnato>";
      stipendio = 0.0f;
      categoria = 0;
   }

   public Persona(String nome, String cognome, String ruolo, float stipendio, byte categoria) {
      this.nome = nome;
      this.cognome = cognome;
      this.ruolo = ruolo;
      this.stipendio = stipendio;
      this.categoria = categoria;
   }
}

Prendiamo in considerazione ora gli alunni: senza dubbio sono persone. Sono persone un po' particolari perché oltre le caratteristiche comuni a tutte le persone, questi frequentano una classe, possono frequentare le lezioni di religione oppure di attività alternativa e quindi possiamo individuare le nuove proprietà: la prima è la classe frequentata, l'altra è la situazione in merito alla religione. Nei linguaggi ad oggetti possiamo indicare il fatto che gli studenti sono persone con particolari caratteristiche tramite il concetto di ereditarietà: si va a creare una nuova classe, in questo caso Studente, e questa non la si fa a partire daccapo ma si fa in modo che abbia già in partenza tutto ciò che è nella classe Persona. Tutte le proprietà ed i metodi che saranno creati in Studente non saranno riportati nella classe Persona perché la persona generica non le ha. In altre parole la classe Studente estende la classe Persona. Esplicitiamo tutti questi concetti tramite il seguente:

public class Studente extends Persona {
   public String classe;
   public String religione;

   public Studente() {
      super();	// Per la spiegazione di super() guarda il paragrafo che segue
      classe = "<non assegnata>";
      religione = "<indefinito>";
   }

   public Studente(String nome, String cognome, String ruolo, String classe, String religione) {
      super(nome, cognome, ruolo, 0.0f, 0);
      this.classe = classe;
      this.religione = religione;
   }
}

La parola super sta ad indicare la classe dalla quale si è fatta l'estensione, si chiama infatti superclasse o classe padre; nell'esempio qui sopra, Persona è la superclasse. Invece la classe che estende la superclasse si chiama classe estesa o classe figlia. Sempre parlando dell'esempio sopra, la classe figlia è Studente. L'invocazione di super() come metodo sta ad indicare il costruttore, in effetti si fa riferimento al nome della classe con le parentesi dei metodi...
Si utilizza super() in modo tale da invocare il costruttore per le proprietà definite in precedenza, lasciando qui il solo lavoro di dare un valore alle ultime definite. Si osserva che ci sono 2 invocazioni di super(): una senza argomenti per chiamare il primo costruttore di Persona, quello senza argomenti e l'altra con tanti argomenti per chiamare il secondo costruttore di Persona, quello con 5 argomenti.

Osserviamo che nelle 2 classi precedenti tutti i metodi e le proprietà sono definiti pubblici. Nel capitolo oggetti, al paragrafo Organizzare gli oggetti, si diceva però che lasciare le proprietà e alcuni particolari metodi liberamente usabili da altre classi potrebbe non essere un bene. Mettendo il modificatore private sulle proprietà della classe si otterrebbe un errore in quanto la classe estesa non potrebbe accedere alle proprietà della classe padre. La classe derivata è a tutti gli effetti un'altra classe e quindi nulla traspare.
La soluzione viene nella parola chiave protected: sta ad indicare che quanto segue (metodo o proprietà) è visibile solo a tutti i metodi della classe stessa (ovviamente), a tutte le classi che estendono questa e a tutte quelle classi presenti nello stesso pacchetto. Nel momento in cui andremo a dichiarare oggetti, dovremo porre attenzione su cosa dovrà essere pubblico, cosa privato e cosa protetto.
La classe persona a questo punto sarà:

public class Persona {
   protected String nome, cognome;
   protected String ruolo;
   protected float stipendio;
   protected boolean categoria;

   public Persona() {
      nome = cognome = "<indefinito>";
      ruolo = "<non assegnato>";
      stipendio = 0.0f;
      categoria = 0;
   }

   public Persona(String nome, String cognome, String ruolo, float stipendio, byte categoria) {
      this.nome = nome;
      this.cognome = cognome;
      this.ruolo = ruolo;
      this.stipendio = stipendio;
      this.categoria = categoria;
   }
}

Allo stesso modo si potrà realizzare la classe Professore estensione di Persona.

public class Professore extends Persona {
   private String materia;
   private int orePotenziamento;

   public Professore() {
      super();
      materia = "<non assegnata>";
      orePotenziamento = 0;
   }

   public Professore(String nome, String cognome, String ruolo, String materia, String orePotenziamento) {
      super(nome, cognome, ruolo, 1000.0f, 2);
      this.materia = materia;
      this.orePotenziamento = orePotenziamento;
   }
}

prevalenza

Aggiungiamo un metodo a ciascuna classe per avere una stringa che contenga in un testo discorsivo tutte le informazioni dell'oggetto. Il metodo avrà, per comodità, lo stesso nome in tutte le implementazioni: lo chiameremo miPresento() a partire da quello in Persona. Nel momento in cui una classe e la sua superclasse presentano ciascuna un metodo con lo stesso nome e stessi tipi e sequenza di parametri, il metodo della classe nasconde quello della superclasse, quindi verrà eseguito solo quello appartenente alla classe dell'oggetto.

public String miPresento() {
   return "Sono " + nome + " " + cognome + ", sono un " + categorie[categoria] + " con ruolo " + ruolo + " e guadagno " + stipendio + "€.";
}

Nell'implementazione che segue, quella di Professore, è presente l'indicazione @Override che sta ad informare il compilatore che il metodo che segue ha la stessa firma (quindi stesso nome del metodo e stesssa sequenza di tipi di parametri) di quello nella superclasse. Tale indicazione non è abbligatoria ma ci aiuta a evitare di scrivere male il nome del metodo e la sequenza dei tipi dei parametri.

@Override
public String miPresento() {
   return "Sono " + nome + " " + cognome + ", sono un " + categoria + " con ruolo " + ruolo + " e guadagno " + stipendio + "€.";
}

Se si provasse a modificare qualcosa del metodo miPresento() come il nome o aggiungendo un qualsiasi parametro, si otterrebbe un errore.

In sintesi

Quando quello che vogliamo fare non è creare una nuova classe ma aggiungere/modificare delle funzionalità di una oggetto esistente va usata la parola chiave extends nella dichiarazione della classe seguita dal nome della classe che si vuole ampliare/cambiare. In questo caso l'oggetto definito avrà tutte le proprietà e tutti i comportamenti di quello originale con le aggiunte o le modifiche indicate nella classe.

Java è un linguaggio ad ereditarietà singola cioè una classe può estendere soltanto una classe alla volta.

super è il riferimento alla classe padre o superclasse e quindi super() è il riferimento al relativo costruttore, all'interno delle parentesi ci saranno gli argomenti del caso.

Una ulteriore attenzione nel momento della progettazione di una classe va fatta su quali proprietà e metodi si vuole siano visibili alle classi che estenderanno la classe che ora stiamo costruendo. Può essere che alcune proprietà e/o metodi preferiamo renderli privati e quindi la classe estesa non potrà usarli direttamente.

Risparmio vincolato pluriennale

Riprendiamo l'esempio del risparmio vincolato. In quel caso la somma che si otteneva era data alla fine dell'anno di investimento. Ipotizziamo però che riusciamo a trattenerci dallo spendere quei soldi e li manteniamo vincolati per un certo numero di anni, diciamo da 1 a 7. Quale sarà la somma totale alla fine?
Di certo ci appoggeremo al codice già scritto nella classe RisparmioVincolato e quindi creeremo una classe estesa che eredita quanto fatto in RisparmioVincolato. Osserviamo che non c'è bisogno di accedere direttamente alle proprietà della superclasse perché basta usare i getter per prenderne i valori ed i setter per impostarli:

public class RisparmioVincolatoPluriennale extends RisparmioVincolato {
   private byte durataAnni; //facendola privata eventuali classi che estenderanno questa non avranno accesso alla proprietà. Una scelta.

   public RisparmioVincolatoPluriennale() {
      super();
      durataAnni = 1;
   }

   public RisparmioVincolatoPluriennale(String nome, String cognome, float importo, short anno, double interessiPercentuale, byte durataAnni) {
      super(nome, cognome, importo, anno, interessiPercentuale);
      if(durataAnni >=1 && durataAnni <=7) {
         this.durataAnni = durataAnni;
      } else {
         this.durataAnni = 1;
      }
   }

   double fornisciCapitale() {
      if(durataAnni == 0) {
         return 0;
      }
      // La percentuale di interessi che viene pubblicizzata è riferita ad un periodo di un anno.
      // Invochiamo il metodo della superclasse che calcola il nuovo capitale a fine periodo, per il numero di periodi indicato
      double importo = getImporto();
      for(int i = 0; i < durataAnni; i++) {
         setImporto(super.fornisciCapitale());	// viene invocato il metodo della superclasse setImporto() aggiornando il capitale sul quale si fa il calcolo
                                                // deve essere invocato fornisciCapitale() della classe padre, per indicare questo fatto si usa super
      }
      double capitale = getImporto();           // metodo della classe padre
      setImporto(importo);	                    // si rimette l'importo al valore iniziale, come se nulla fosse cambiato
      return capitale;
   }
}

Primo.java

package it.esempio;

public class Primo {
	boolean p;
	int x;
	public int leggiVal(){
		return x;
	}
	public String descrivi(){
		return "n:"+x;
	}
}

Secondo.java

package it.esempio;

public class Secondo extends Primo {
	String c;
	public Secondo(int k, boolean p, String u){
		x=k;
		y=p;
		c=u;
	}
	public String descrivi(){
		return c+x+c;
	}
}

L'oggetto Secondo ha un metodo leggiVal()?

no si perché lo eredita da Primo

Considera il seguente frammento di programma inserito in un'altra classe:
Secondo o;
o = new Secondo(3,false,"#");
String m=o.descrivi();

Quanto vale m?

non si sa 3 n:3 attenzione: descrivi() viene ridefinito in Secondo #3# perché viene usato il metodo definito in Secondo