Paola Magillo, Univestita' di Genova, Corso di Programmazione II per SMID, a.a. 2004-2005.

Lezione 03:

CLIENTSHIP E INHERITANCE

Oggetto nullo

La parola chiave null denota oggetto nullo, inesistente.

Esempi di inizializzazione per una variabile oggetto:
es.1: Rectangle r = new Rectangle(); la variabile r punta a un nuovo rettangolo appena costruito (cioe' contiene il suo OID).
es.2: Rectangle r = null; la variabile r punta a nessun oggetto.

E' errore accedere agli attributi dell'oggetto nullo o chiamare metodi sull'oggetto nullo.

Se ho inizializzato r come nell'es.2, poi scrivere r.width oppure r.area() provoca errore.

Classe che ne usa un'altra

Nella classe rettangolo (ved. lezione scorsa) gli attributi erano a valori di tipi base (interi).
In generale gli attributi possono essere a valori oggetti di altre (o anche della stessa!) classe.
Esempio: classe coppia di rettangoli (ved. lezione scorsa).

Clientship (clientela) = quando una classe definisce un attributo che e' di tipo classe.
La classe A che ha un attributo di classe B e' cliente di B.
Un oggetto di classe A ha per sotto-componente un oggetto di classe B.

Esempio: RectanglePair ha attributi di classe Rectangle, e' cliente di Rectangle.

Nota per i costruttori

Ogni classe ha un costruttore di default che crea una nuova istanza con tutti gli attributi inizializzati a valore nullo, poi posso definire un costruttore io... (ved. lezione scorsa).
Valore nullo e' zero per i tipi numerici, false per il tipo boolean. Per i tipi classe e' null, l'oggetto nullo.

Classe che usa se stessa

Una classe puo' essere cliente di se stessa. Puo' avere come attributi oggetti della stessa classe (classe definita ricorsivamente).

Esempio: il treno coi vagoni

Un treno e' fatto di tanti vagoni. Ogni vagone ha un numero e puo' "agganciare" un altro vagone.
Il treno aggancia il primo vagone. Ha inoltre un tipo di treno (regionale, inter-regionale ecc.), un numero di treno, una stazione di partenza e una di arrivo.

class Vagone
{
  int numero;      /* numero di carrozza */
  Vagone prossimo; /* collegamento al prossimo vagone */
  Vagone(int num)  /* crea il vagone isolato */
  { numero = num; prossimo = null; }
  void aggancia(Vagone vag) { prossimo = vag; }
}

class Treno
{
  static final int REGIONALE = 1;
  static final int INTER_REGIONALE = 2;
  /* eccetera per altri tipi di treno... */
  int tipo;     /* tipo di treno */
  int numero;   /* numero del treno */
  Vagone primo; /* collegamento al primo vagone */
  String stazione_partenza;
  String stazione_arrivo;
  Treno(int tipo, int num, String partenza, String arrivo)
  /* crea treno senza vagoni */
  {
    tipo_treno = tipo;
    numero = num;
    stazione_partenza = partenza; 
    stazione_arrivo = arrivo;
    primo = null;
  }
  void aggancia(Vagone vag) { primo = vag; }
}

La classe Treno e' cliente di Vagone. Vagone e' cliente di se stessa. Vagone e' classe ricorsiva perche' contiene riferimento a se stessa.

Esempio di uso:

1: Treno t = new
     Treno(Treno.INTER_REGIONALE, 1267, "Genova", "Ventimiglia");
2: Vagone ultimo;
3: Vagone v1 = new Vagone(210);
4: t.aggancia(v1);
5: ultimo = v1;
6: v1 = new Vagone(211);
7: ultimo.aggancia(v1);
8: ultimo = v1;
   /* posso attaccare altri vagoni ripetendo le linee di
      codice 6,7,8 con diversi numeri di carrozza */
Aggancio prima il primo vagone e poi dietro tutti gli altri, ogni volta agganciando un nuovo vagone all'ultimo vagone. La variabile ultimo punta sempre l'ultimo vagone e viene spostata ogni volta. La variabile v1 serve a creare un nuovo vagone, che poi sara' agganciato e diventera' l'ultimo.

Attenzione:

Stampare un treno

Il treno stampa il suo tipo, il suo numero, le due stazioni di partenza e arrivo, poi dovrebbe stampare i vagoni...
Il treno dice al primo vagone di stampare se stesso. Questo pensera' a far stampare i vagoni che gli si trovano agganciati dietro, fino all'ultimo.
Ogni vagone per stamparsi stampa il suo numero di carrozza, poi se ha agganciato un altro vagone dice a questo di stamparsi, se invece non ha agganciato nulla (ultimo vagone) non fa altro.

Nella classe Vagone:

  void print()
  {
    System.out.println("carrozza numero " + numero);
    if (prossimo!=null)  prossimo.print();
  }

La stampa di un vagone e' funzione ricorsiva. Ciascun vagone stampa quello seguente, il quale stampa quello dopo ancora... e cosi' ricorsivamente... ci si ferma quando si incontra il vagone nullo.

Nella classe Treno:

  void print()
  {
    System.out.print("Treno ");
    switch (tipo_treno)
    {  case REGIONALE: System.out.print("regionale"); 
                       break;
       case INTER_REGIONALE: System.out.print("inter-regionale");
                             break;
       default: System.out.print("non classificato");
                break;
    }
    System.out.println(" numero " + numero + " vagoni:");
    if (primo!=null) primo.print();
  }

Ereditarieta' (inheritance)

Corrisponde all'idea di un tipo che e' raffinamento di un altro tipo, cioe' gli aggiunge qualcosa.

Una sotto-classe raffina / estende / arricchisce una super-classe aggiungendo nuove variabili e/o nuove funzioni, oppure ridefinendo l'implementazione (cioe' sostituendo il body) di funzioni gia' presenti nella super-classe.

Esempio: Classe "rettangolo colorato" aggiunge il colore al rettangolo. Eredita tutte le altre variabili e funzioni dalla classe "rettangolo".

import java.awt.*;
class ColoredRectangle extends Rectangle
{
  Color col;
  // attributi length e height sono ereditati
  ColoredRectangle(int l, int h, Color c)
  { super(l,h); col = c; }
  // tutti gli altri metodi sono ereditati
}
Java ha classe predefinita Color per il colore, che si trova nel package AWT (bisogna importarlo all'inizio del file). Rappresenta un colore come terna di componenti RGB (red, blue, green).

Nota: la sottoclasse deve ridefinire il costruttore (che non viene ereditato). Nel costruttore della sotto-classe e' possibile (non obbligatorio) richiamare quello della superclasse usando "super".

In generale, quando l'implementazione di un metodo viene ridefinita nella sottoclasse, si puo' (se opportuno) chiamare l'implementazione di quel metodo nella superclasse tramite la parola chiave "super", che denota questo stesso oggetto visto come appartenente alla superclasse.
Es. metodo di stampa del rettangolo colorato:

  void print()
  { super.print(); // stampa il rettangolo
    System.out.println("colore: " + col); // stampa il colore
  }

Terminologia:

Esempio: il treno coi vagoni

Il treno ha tutto cio' che ha un vagone: un numero, un vagone agganciato, funzioni per aggancio e sgancio. In piu' ha tipo del treno e le stazioni di partenza e arrivo.
Possiamo rifare il treno come sottoclasse del vagone.

class Treno extends Vagone
{
  static final int REGIONALE = 1;
  static final int INTER_REGIONALE = 2;
  /* eccetera per altri tipi di treno... */
  int tipo_treno;
  // il numero viene ereditato dal vagone
  // per primo vagone viene usato il prossimo vagone del vagone
  String stazione_partenza;
  String stazione_arrivo;
  Treno(int tipo, int num, String partenza, String arrivo)
  /* crea treno senza vagoni */
  {
    super(num); // chiama costruttore del vagone
    tipo_treno = tipo;
    stazione_partenza = partenza; 
    stazione_arrivo = arrivo;
  }
  // metodo aggancia e' ereditato da Vagone
  void print()
  /* metodo print e' ereditato da Vagone ma re-implementato */
  {
    System.out.print("Treno ");
    switch (tipo_treno)
    {  case REGIONALE: System.out.print("regionale");
                       break;
       case INTER_REGIONALE: System.out.print("inter-regionale");
                             break;
       default: System.out.print("non classificato");
                break;
    }
    System.out.println(" numero " + numero + " vagoni:");
    if (prossimo!=null) prossimo.print();
  }
}

Relazione "is-a"

Se B e' sottoclasse di A, allora tutti gli oggetti di B sono anche oggetti di A.

Un oggetto di classe B e' un (is a) oggetto di classe A.

Esempio: un rettangolo colorato e' anche un rettangolo.
Un treno e' anche un vagone!

Dovunque si puo' usare un oggetto della super-classe si puo' usare anche un oggetto della sottoclasse.
Se B e' sottoclasse di A posso mettere un oggetto di classe B dentro una variabile di classe A.

Rectangle r = new ColoredRectangle(12,6,Color.red);
Vagone v = new Treno(Treno.INTER_REGIONALE, 1267, "Genova", "Ventimiglia");

Nonostante l'oggetto sia della sotto-classe, finche' sta in variabile della super-classe gli posso applicare solo metodi della super-classe. Quello che la sottoclasse ha aggiunto viene "dimenticato".

Binding

Binding = associazione tra nome di funzione e sua implementazione.
Quando viene chiamata una funzione che ha implementazione diversa nella super-classe e nella sotto-classe, come viene determinata l'implementazione da usare? quella della super- o quella della sotto-classe?

Esempio:

Vagone v;
v = new Treno(Treno.INTER_REGIONALE, 1267, "Genova", "Ventimiglia");
v.print();

Java ha binding dinamico.

Visibilita' di attributi e metodi

Variabili e funzioni oltre che private e pubbliche possono essere protette: le puo' usare solo la classe stessa oppure le sue sottoclassi. Quelle private le puo' usare solo la classe stessa. Riassumendo:

Nell'esempio del rettangolo e del rettangolo colorato:
Se in Rectangle dichiaro privati gli attributi length e heigth, non li posso usare nel corpo di funzioni della sottoclasse ColoredRectangle.
Meglio dunque dichiararli protetti.