Ereditarietà

Sappiamo come definire nuove classi.

In alcuni casi, abbiamo già una classe 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


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. Ingegneria 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;
}

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;
}

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);
  }
}

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

Ma (1,2) non è il centro!


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 l'incapsulamentoè 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.


Esercizio

Definire una classe PointCol che estende Point con l'aggiunta di una componente intera colore e con un metodo che calcola la distanza del punto dall'origine.


Soluzione

Si aggiunge il metodo alla classe.

import java.awt.*;

class PointCol extends Point {
  int colore;

  void stampaDistanza() {
    double d;

    d=Math.sqrt(this.x*this.x+this.y+this.y);

    System.out.println(d);
  }
}

Extends

Per ora diciamo solo che fare class NomeClasse extends AltraClasse inserisce all'interno della classe NomeClasse tutte le componenti e i metodi di AltraClasse

In realtà, è più complicato.


Esercizio

Realizzare la classe SPoint che estende Point con una componente intera z, e che la in più il metodo setToOrigin che pone il punto di invocazione nell'origine (tutte e tre le componenti vanno a zero).


Soluzione

Nuova componente+nuovo metodo.

import java.awt.*;

class SPoint extends Point {
  int z;

  void setToOrigin() {
    this.x=0;
    this.y=0;
    this.z=0;
  }
}

Soluzione alternativa

Tutti i metodi della classe Point sono anche metodi di SPoint.

Quindi su un oggetto SPoint posso invocare il metodo move

Fa la però esattamente la stessa cosa che fa su un Point: cambia le componenti x e y.

import java.awt.*;

class SPoint extends Point {
  int z;

  void setToOrigin() {
    this.move(0,0);
    this.z=0;
  }
}

Si può fare perchè this è un oggetto della classe SPoint, e la classe SPoint ha il metodo move


Un esempio di programma

Creo due punti, e su uno invoco il metodo.

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

    p.move(3,4);
    p.z=-3;

    r.move(-1,-1);
    r.z=-1;

    p.setToOrigin();

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

Esercizio

Realizzare la classe RectPoint che estende Rectangle con una componente centro di tipo Point, e ha in più un metodo setCentro che mette nel centro l'effettivo centro del rettangolo.

Si assuma che il punto sia già stato creato.


Soluzione

I valori del rettangolo sono this.x this.y this.width this.heigh this.centro

I valori calcolati vanno messi nel punto this.centro

import java.awt.*;

class RectPoint extends Rectangle {
  Point centro;

  void setCentro() {
    this.centro.x=this.x+this.width/2;
    this.centro.y=this.y+this.height/2;
  }
}

Soluzione: programma di prova

Prima metto i valori del rettangolo, poi creo il punto e poi invoco il metodo

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.setCentro();

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

Viene stampato il centro

Nota: l'oggetto punto va creato. Se non lo crea il metodo, lo deve creare il programma.


Definizione di metodi con argomenti

Definire il metodo move per SPoint, che modifica tutte e tre le coordinate.


Soluzione

move è simile a setToOrigin, solo che i valori da mettere nelle componenti sono gli argomenti, e non 0

import java.awt.*;

class SPoint extends Point {
  int z;

  void move(int a, int b, int c) {
    this.x=a;
    this.y=b;
    this.z=c;
  }
}

Programma di prova

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

    SPoint r;
    r=new SPoint();

    p.move(3,4);
    r.move(-1,-1,2);

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

Notare:

Se in SPoint non definisco move allora ho quello ereditato da Point

Il metodo move è stato ridefinito.


Costruttori

I costruttori non si ereditano.

Esempio: Point ha un costruttore a due argomenti:

Point p=new Point(2,3);

La classe SPoint ha solo il costruttore standard (quello a due argomenti)

Se voglio un costruttore, lo devo definire

import java.awt.*;

class SPoint extends Point {
  int z;

  SPoint(int x, int y, int z) {
    this.x=x;
    this.y=y;
    this.z=z;
  }
}

Esercizio

Estendere la classe Point in modo che abbia un nuovo costruttore, che prende come argomento un rettangolo, e mette nel punto il suo centro.

Nota: la nuova classe si chiama NewPoint


Soluzione

Per estendere serve un nuovo nome:

class NewPoint extends Point {
  ...
}

Si comincia con l'intestazione del metodo come al solito.

import java.awt.*;

class NewPoint extends Point {
  NewPoint(Rectangle r) {
    ...
  }
}

Definizione del costruttore

Si tratta quasi di un metodo come gli altri.

import java.awt.*;

class NewPoint extends Point {
  NewPoint(Rectangle r) {
    this.x=r.x+r.width/2;
    this.y=r.y+r.height/2;
  }
}

Calcolo, sulla base degli argomenti, i valori da mettere nelle componenti dell'oggetto appena creato.


Programma di prova

import java.awt.*;

class ProvaNewPoint {
  public static void main(String args[]) {
    Rectangle r;
    r=new Rectangle(10,10,20,30);

    NewPoint q;
    q=new NewPoint(r);

    System.out.println(q);
  }
}

I costruttori si ereditano?

I costruttori non si ereditano.

import java.awt.*;

class ProvaNewPoint {
  public static void main(String args[]) {
    NewPoint p;
    p=new NewPoint(2,4);

    NewPoint q;
    q=new NewPoint();

    System.out.println(p);
  }
}

Le due new NewPoint(...) danno errore.


Ridefinizione dei costruttori

Se voglio un costruttore, lo devo definire.

import java.awt.*;

class NewPoint extends Point {
  NewPoint() {
  }

  NewPoint(int x, int y) {
    this.x=x;
    this.y=y;
  }

  NewPoint(Rectangle r) {
    this.x=r.x+r.width/2;
    this.y=r.y+r.height/2;
  }
}

Ereditare i costruttori

Se voglio solo ``ereditare'' il costruttore: definisco il costruttore con super(args) come corpo:

import java.awt.*;

class NewPoint extends Point {
  NewPoint() {
    super();
  }

  NewPoint(int x, int y) {
    super(x, y);
  }

  NewPoint(Rectangle r) {
    this.x=r.x+r.width/2;
    this.y=r.y+r.height/2;
  }
}

Di solito, conviene ridefinire il costruttore.


Il costruttore della sovraclasse

super(argomenti) invoca un costruttore della classe che è stata estesa

Quando si invoca il costruttore della superclasse, questa deve essere la prima istruzione del metodo.

import java.awt.*;

class TriPoint extends Point {
  int z;

  TriPoint() {
  }

  TriPoint(int x, int y, int z) {
    super(x, y);
    this.z=z;
  }
}

Quando un metodo non inizia con super(argomenti), si assume automaticamente super(), ossia il costruttore senza argomenti.


Nota

Tutti i costruttori, se non iniziano con super(argomenti), si considerano come se iniziassero con super().

Quindi, se si definisce una classe senza il costruttore senza argomenti, allora nelle sottoclassi bisogna chiamare esplicitamente super(argomenti) come prima istruzione.

Altrimenti, lui assume super(), che non esiste.


Accesso ed ereditarietà

Cosa succede se cambio i diritti di accesso in una sottoclasse?

variabili
quando definisco una variabile con lo stesso nome, sto creando un'altra variabile: si può fare
metodi
quando definisco un metodo con lo stesso nome, sto ridefinendo un metodo; si può dare un diritto di accesso più liberale ma non più restrittivo

Estendere i diritti

Si può sempre fare:

class Sopra {
  private int y;

  private int getY() {
    return this.y;
  }
}
class Sotto extends Sopra {
  public int y;

  public int getY() {
    return this.y;
  }
}

Funziona tutto.


Restringere i diritti

Si può fare con le variabili, perchè tanto sono variabili nuove (e le vecchie esistono ancora)

Non si può fare con i metodi:

class Sopra {
  public int x;

  public int getX() {
    return this.x;
  }
}
class Sotto extends Sopra {
  private int x;        // ok

  private int getX() {  // errore!
    return this.x;
  }
}

La definizione del metodo getX in Sotto dà errore.