Ereditarietà

Abbiamo visto come definire nuovi tipi di dato.

In alcuni casi, abbiamo già un tipo che va quasi bene, ma manca qualcosa.

Esempio: Point è un punto sul piano, manca la coordinata z

  Point p;
  p=new Point();

  p.z=12;
  // non funziona: i punti non hanno
  // la componente z

Vediamo come si fa ad aggiungere componenti a tipi già definiti.


Progettazione: cosa manca?

Si parte da una classe già definita

Si elencano le componenti che mancano.

Nel nostro caso, manca una componente int di nome .z


Progettazione: nuovo nome

Quando si estende una classe, in realtà di crea una classe nuova.

Non si può usare il nome di un tipo già esistente.

Nel nostro caso, non si può usare Point

Scegliamo per esempio il nome SPoint


Progettazione: programma di prova

Il nome del nuovo tipo è SPoint

Sappiamo quali sono le componenti.

Questo è un programma che usa la classe.

import java.awt.*;

class TriDist {
  public static void main(String args[]){
    SPoint p, g;
    p=new SPoint();
    g=new SPoint();

    p.x=10;
    p.y=12;
    p.z=5;
  }
}

Vogliamo definire la classe in modo che questo programma sia corretto.


Progettazione: descrizione a parole

Voglio dire una cosa del genere:

Crea nuova classe SPoint, che ha
le stesse cose di Point piu':
| componente intera di nome .z

Progettazione: traduzione nel linguaggio

Crea nuova classe SPoint, che ha
le stesse cose di Point piu':
| componente intera di nome .z

Nel linguaggio di programmazione, questo si traduce cosí:

class SPoint extends Point {
  int z;
}

Il tipo Point viene usato
quindi occorre fare import java.awt.*;

Definizione completa:

import java.awt.*;

class SPoint extends Point {
  int z;
}

Va messa in un file SPoint.java


Compilazione ed esecuzione

Si compilano tutti i file

javac SPoint.java
javac TriDist.java

Il file che contiene le istruzioni è TriDist

java TriDist

Ereditarietà

Informalmente, abbiamo detto:

  1. ``estendere'' una classe
  2. creare una nuova classe che ne estende un'altra

Formalmente, questo meccanismo si chiama ereditarietà


E la classe di partenza?

Si crea una nuova classe; quella di partenza resta inalterata.

Si può ancora usare la classe di partenza, che non è stata modificata.

import java.awt.*;

class DuePunti {
  public static void main(String args[]){
    SPoint p;
    p=new SPoint();

    p.x=12;
    p.y=3;
    p.z=-43;

    Point f;
    f=new Point();

    f.x=12;
    f.y=3;

    // f.z=-43; e' un errore: gli oggetti
    // Point non hanno questa componente
  }
}

Il tipo Point si comporta come al solito.


Traduzione inversa

Data la definizione, passare al tipo

class SPoint extends Point {
  int z;
}

Traduzione meccanica:

Crea nuova classe SPoint, che ha
le stesse cose di Point piu':
| componente intera di nome z

Dal momento che Point ha le due componenti x e y

Crea nuova classe SPoint con
| componente intera di nome x
| componente intera di nome y
| componente intera di nome z

Traduzione inversa: esercizio

Siano date le due classi seguenti:

Abcd.java          Efgh.java
class Abcd {
  int x;
  int y;
}
class Efgh extends Abcd {
  double d;
  int g;
}

Dire come sono fatti i dati di tipo Abcd e Efgh

Scrivere un programma che usa queste due classi.


Soluzione: Abcd

Gli oggetti di tipo Abcd hanno due componenti
x ed y, di tipo intero.

Quando si crea una classe che estende un'altra,
quella precedente resta inalterata

Se a è di tipo Abcd,
allora a.d e a.g non esistono.

Efgh.java crea una nuova classe,
non modifica la classe Abcd


Soluzione: Efgh

Gli oggetti della nuova classe hanno le stesse componenti di Abcd, più d reale e g intero.

Efgh.java equivale a dire:

Crea un nuovo tipo Efgh con
| componente intera di nome x
| componente intera di nome y
| componente reale di nome d
| componente intera di nome g

Le prime due componenti sono definite in Abcd.java

Le ultime due componenti sono definite in Efgh.java


Soluzione: esempio di programma

Sia Abcd che Efgh sono tipi validi.

Posso definire variabili e creare oggetti di questi due tipi.

class Prova {
  public static void main(String args[]){
    Abcd a;
    a=new Abcd();

    Efgh b;
    b=new Efgh();

	// a ha le componenti x e y
    a.x=12;
    a.y=3;
	// a.d=10.2; e' un errore!
	

	// b ha anche d g
    b.x=4;
    b.y=-1;
    b.d=10.2;
    b.g=3;

    System.out.println(a.x);
  }
}

Notare che a.d=10.2 è un errore:
gli oggetti di tipo Abcd non hanno la componente d

Questa componente c'è solo per la classe Efgh
(che estende Abcd)


Esercizio

Estendere la classe Studente in modo che includa il nome del corso di laurea (es. Ingegeria Gestionale, ecc.) e il numero di esami sostenuti.


Passo 1: definire la classe a parole

Nuova classe = nuovo nome

Scelgo NuovoStud

Componenti: gli stessi di Studente più corso ed esami

crea una nuova classe NuovoStud
con tutte le parti di Studente e
| componente String di nome corso
| componente int di nome esami

Passo 2: programma di prova

Basta un qualsiasi programma che usa la classe.

class DBUniv {
  public static void main(String args[]){
    NuovoStud x;
    x=new NuovoStud();

    x.nome="Ciccio";
    x.media=18;
    x.corso="Ing. gestionale";
    x.esami=2;

    System.out.println(x.nome);
    System.out.println(x.media);
  }
}

La nuova classe ha due componenti in più rispetto a Studente


Passo 3: definizione della classe

Si ottiene dicendo quali campi in più ci sono rispetto alla classe che viene estesa.

class NuovoStud extends Studente {
  String corso;
  int esami;
}

Passo 4: compilazione ed esecuzione

In ordine:

javac Studente.java
javac NuovoStud.java
javac DBUniv.java

java DBUniv

Solo l'ultimo file contiene le istruzioni.


I metodi della nuova classe

I metodi vengono ereditati

Fanno letteralmente le stesse cose!

I metodi non vengono estesi

classe Point
move cambia i valori di x ed y
classe SPoint
move esiste, e cambia solo i valori di x ed y

Perchè i metodi non si estendono automaticamente?

Per il programmatore può essere ovvio che va cambiata anche la z

Per il calcolatore non ci sono cose ovvie

Perchè?

Il calcolatore non sa quale è il significato delle componenti
(per lui sono solo zone di memoria)

La componente z potrebbe indicare il colore,
per quello che ne sa lui
(e quindi move non la deve modificare)

Vedremo poi come modificare i metodi


Esercizio

Estendere la classe Rectangle in modo che ogni rettangolo contenga anche il punto centrale (usare Point)


Soluzione parte 1: definizione a parole

Voglio una classe che ha tutte le componenti di Rectangle più un punto.

Crea nuova classe RectPoint con
tutte le componenti di Rectangle e:
| componente Point di nome centro

Soluzione parte 2: traduzione in Java

Dato che la classe è cosí semplice, la traduciamo subito:

import java.awt.*;

class RectPoint extends Rectangle {
  Point centro;
}

Attenzione! È un oggetto con dentro una variabile di tipo oggetto

La new va fatta per tutti gli oggetti


Soluzione parte 3: programma di prova

Gli oggetti di tipo RectPoint sono simili ai rettangoli, ma hanno una componente in più, di tipo Point

import java.awt.*;

class ProvaRectPoint {
  public static void main(String args[]){
    RectPoint r;
    r=new RectPoint();

    r.setBounds(23,12,4,5);
    // modifica solo le componenti
    // originarie x y width height

    r.centro=new Point();
    r.centro.move(1,2);

    System.out.println(r.x);
    System.out.println(r.centro.x);
  }
}

Dopo r=new RectPoint(); esiste la variabile r.centro (la casellina) ma non esiste l'oggetto (il rettangolone con le due caselline x y dentro)

Prima di poter usare r.centro.x oppure r.centro.move(), devo fare new Point()

Ma (1,2) non è il centro! (ci torniamo dopo)


Stato della memoria 1

RectPoint r;

Viene creata solo la variabile

Non esistono r.x, r.y, ecc e non si può fare r.setBounds()


Stato della memoria 2

r=new RectPoint();

Viene creato l'oggetto RectPoint, con le sue componenti dentro.

Ci sono sia le componenti di Rectangle che quelle definite in più per RectPoint


Stato della memoria 3

r.setBounds(23,12,4,5);

Vengono messi quei valori nell'oggetto

L'oggetto puntato da r ora esiste e si può usare.

La variabile r.centro però non punta nessun oggetto.


Stato della memoria 4

    r.centro=new Point();

Viene creato l'oggetto punto:

A questo punto, si può fare la move oppure usare le componenti per mettere i valori dentro.

Notare la posizione di r.x e di r.centro.x

Anche se sono due "componenti x", una è una componente di un rettangolo, l'altra del punto che fa parte del rettangolo.

Quando si hanno dubbi sulla componenti, fare un diagramma del genere.


Ma quel punto non è il centro!

Chi definisce la classe pensave di usare il punto per memorizzare il centro.

Il programma può mettere un punto qualsiasi in r.centro

Per mettere il centro, si può fare cosí:

import java.awt.*;

class ProvaCentro {
  public static void main(String args[]) {
    RectPoint r;
    r=new RectPoint();

    r.setBounds(23,12,4,5);
    // modifica solo le componenti
    // originarie x y width height

    r.centro=new Point();
    r.centro.move(r.x+r.width/2,r.y+r.height/2);

    System.out.println(r.x);
    System.out.println(r.centro);
  }
}

Non c'è niente che impedisca a chi scrive il programma di sbagliare.

Con i metodi, è possibile evitare che questo avvenga.


Ereditarietà: ridefinizioni

Con il meccanismo di estensione, è anche possibile ridefinire le componenti.

Esempio: classe originaria Studente

class Studente {
  String nome;
  int media;
}

Non mi piace perchè la media è intera ma dovrebbe essere reale.

class MStud extends Studente {
  double media;
}

Nella classe MStud, la componente media è reale.

Le altre componenti sono come nella classe di partenza.

class ProvaMStud {
  public static void main(String args[]){
    MStud s;
    s=new MStud();

    s.nome="Pippo";  // stringa come al solito
    s.media=18.01;   // valore reale
  }
}

Ridefinizione: esercizio

Definire una classe che estende Rectangle in questo modo:

  1. le componenti x e y diventano reali
  2. viene aggiunta una componente intera colore

Soluzione

Diamo direttamente la soluzione:

import java.awt.*;

class RectCol extends Rectangle {
  double x;
  double y;
  int colore;
}

Le componenti x ed y vengono ridefinite come reali

colore viene definito come intero


Uso delle classi con ridefinizione

Per le componenti, non ci sono problemi.

I metodi possono avere un comportamento inatteso.

import java.awt.*;

class ProvaRectCol {
  public static void main(String args[]){
    RectCol r, q;
    r=new RectCol();

    r.x=12.2;
    r.y=4.2;
    r.width=12;
    r.height=32;

    // r.setBounds(12.2, 4.2, 12, 32);
    // errore: gli argomenti
    // devono essere interi
    r.setBounds(1, -2, 12, 32);

    System.out.println(r.x);
    System.out.println(r.getX());
  }
}

Sembra ovvio che i primi due argomenti di setBounds possano essere reali.

Invece, setBounds vuole comunque due interi.


Cosa si stampa?

Viene stampato:

12.2
1.0

In altre parole, r.x ed r.getX() non sono più la stessa cosa.

(nota: il metodo getX restituisce un reale, per questo viene stampato 1.0 e non 1)


Ridefinizioni: cosa succede realmente

Le componenti di un oggetto non possono venire cancellate o ridefinite facendo una estensione.

Quello che succede è che vengono soltanto aggiunte nuove componenti con lo stesso nome.

nuova classe RectCol 
| componente intera x
| componente intera y
| componente intera width
| componente intera height
|
| componente double x
| componente double y
| componente intera color

Ci sono due componenti di nome x

Una è ereditata da Rectangle (ossia esiste solo perchè c'è in Rectangle)

L'altra è definita nella estensione, ossia solo in RectCol


Ridefinizione: la memoria

All'interno dell'oggetto, si sa quali parti sono del tipo originario e quali sono definite nell'estensione.

Ci sono due r.x. Quale viene usata?

Regole di accesso in caso di conflitti:

Quindi: r.x è la variabile di tipo double, perchè è introdotta dalla estensione.

Invece, i metodi di Rectangle, come setBounds() e getX(), usano la componente x intera, perchè questa sta nella "parte Rectanngle" dell'oggetto.

Per evitare confusione: se si ridefiniscono le componenti, vanno ridefiniti anche i metodi che le usano.