ereditarieta

oggetti che ampliano o specializzano oggetti preesistenti

La modalità di creare oggetti che vediamo in questo capitolo è quella che ci permette sia di scrivere meno codice ripetitivo che di (potenzialmente) avere meno errori creando modelli della realtà e utilizzando parti di programma già scritte.

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, l'anno di nascita e il numero di telefono. La classe, con relativi costruttori, sarà una cosa del tipo:


public class Persona {
   public String nome;
   public String cognome;
   public int annoNascita;
   public String telefono;

   public Persona() {
      nome = "indefinito";
      cognome = "indefinito";
      annoNascita = -1;
      telefono = "indefinito";
   }

   public Persona(String nome, String cognome, int annoNascita, String telefono) {
      this.nome = nome;
      this.cognome = cognome;
      this.annoNascita = annoNascita;
      this.telefono = telefono;
   }
}

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 da zero ma si fa in modo che abbia già in partenza tutto ciò che è nella classe Persona. 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 boolean religione; // false in caso si avvalga dell'attività alternativa

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

   public Studente(String nome, String cognome, int annoNascita, String classe, boolean religione) {
      super(nome, cognome, annoNascita, "");
      this.classe = classe;
      this.religione = religione;
   }
}

In questo esempio Persona è la superclasse (o classe padre) di Studente che viene detta classe figlia.

La parola super sta ad indicare la superclasse, la chiamata del metodo super() è la chiamata del costruttore della superclasse. Si utilizza super() in modo tale da invocare il costruttore per le proprietà definite nella superclasse (che magari potrebbero essere private), 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 quattro argomenti per chiamare il secondo costruttore di Persona, quello con quattro 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 Persona 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 di ciò che è definito privato 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 al livello di visibilità di ogni metodo e proprietà.
La classe persona a questo punto sarà:


public class Persona {
   protected String nome;
   protected String cognome;
   protected int annoNascita;
   protected String telefono;

   public Persona() {
      nome = "indefinito";
      cognome = "indefinito";
      annoNascita = -1;
      telefono = "indefinito";
   }

   public Persona(String nome, String cognome, int annoNascita, String telefono) {
      this.nome = nome;
      this.cognome = cognome;
      this.annoNascita = annoNascita;
      this.telefono = telefono;
   }

   /* da aggiungere i metodi get/set per le proprietà */
}

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 che andrà ad estenderla non potrà usarli direttamente.

Allo stesso modo si potrà realizzare la classe Professore estensione di Persona che tiene conto di due caratteristiche aggiuntive: la materia insegnata e lo stipendio.


public class Professore extends Persona {
   protected String materia;
   private double stipendio;

   public Professore() {
      super();
      materia = "?";
      stipendio = 0;
   }

   public Professore(String nome, String cognome, String materia, double stipendio) {
      super(nome, cognome, -1, "");
      this.materia = materia;
      this.stipendio = stipendio;
   }
   
   // permette di leggere la proprietà stipendio che è privata
   public double getStipendio(){
      return stipendio;
   }
   
   // volutamente non c'è setStipendio
}

Chi ha progettato la classe Professore ha ad esempio deciso che lo stipendio una volta creato l'oggetto non si può più modificare ma si può soltanto leggere.

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 estende sempre e soltanto un'altra classe.

In caso che la parola chiave extends non sia presente la classe che si sta definendo extenderà java.lang.Object. Ovviamente questo meccanismo forma delle catene: la classe A estende la classe B che a sua volta estende C e così via. Quando si invoca un metodo nell'oggetto di tipo A questo potrebbe essere definito sia in A... che in B che in C. Java cercherà prima il metodo in A, se non c'è guarderà in B, se non c'è guarderà in C e alla fine in Object.

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.

Quanti tipi ha un oggetto?

La risposta immediata è: uno! Purtroppo però non è del tutto vero: ovviamente un oggetto è del tipo che si è scritto nella dichiarazione:


Studente mario = new Studente();

mario è di tipo Studente ma... non è anche una Persona? Si, lo è, in effetti è perfettamente legale scrivere sotto alla riga precedente


Persona anonimo = mario;

In questo modo però utilizzando la variabile anonimo potrò utilizzare soltanto i metodi definiti in Persona o una sua superclasse, non più quelli di Studente. È altresì possibile usare la variabile mario in tutti i contesti in cui serve di usare una Persona. Questo spiega perché usando JavaFX per aggiungere un elemento ad una griglia usando il metodo add() si possono inserire molte cose diverse, la dichiarazione del metodo dice: add(Node figlio, int indiceColonna, int indiceRiga)

Sia Button che Label sono sottoclassi di Node quindi possono essere utilizzate nel metodo add, in verità Label (e neanche Button) non è una sottoclasse diretta di Node, la gerarchia completa è: Object→Node→Parent→Region→Control→Labeled→Label Però vista la gerarchia Label è anche un Node.

Esercizio

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