Lezione 4 · Canale 1 · giovedì 1 ottobre 2026
Ereditarietà e incapsulamento in Java
Progettazione del Software
Sottoclassi: la gerarchia delle figure
Una classe ne estende un'altra con extends: la classe base (superclasse) fornisce campi e metodi, la classe derivata (sottoclasse) li eredita e può aggiungerne. Per decidere se usare extends ci si chiede se gli oggetti della nuova classe sono un tipo particolare di quelli dell'altra. Se la risposta è sì, si estende.
Figura ha tre sottoclassi, Ellisse, Rettangolo e Triangolo. Un quadrato è un rettangolo con i lati uguali e un cerchio è un'ellisse particolare: Quadrato e Cerchio non hanno campi in più rispetto alla loro superclasse.
Il verso dell'estensione conta. Se Rettangolo estendesse Quadrato, in una variabile di tipo Quadrato si potrebbe mettere un rettangolo qualsiasi.
public class Figura {
String descrizione;
public Figura(String descrizione) {
this.descrizione = descrizione;
}
public String toString() {
return "Figura [descrizione=" + descrizione + "]";
}
}
Rettangolo aggiunge i campi interi base e altezza. Per l'area basta usare i dati già presenti: un rettangolo di base 2 e altezza 1 ha area 2.
public class Rettangolo extends Figura {
int base, altezza;
public Rettangolo(String descrizione, int base, int altezza) {
super(descrizione);
this.base = base;
this.altezza = altezza;
}
public double area() {
return base * altezza;
}
public double perimetro() {
return 2 * (base + altezza);
}
}
Con lati 4 e 5 il perimetro è .
Costruttori delle sottoclassi
Un oggetto della sottoclasse contiene anche i campi ereditati, e alcuni possono essere inaccessibili alla sottoclasse. Perciò il suo costruttore chiama un costruttore della superclasse con super(...), che deve essere la prima istruzione.
- Senza una chiamata esplicita, Java invoca
super(), il costruttore senza parametri: se la superclasse non lo ha, c'è un errore di compilazione. - Una classe senza costruttori riceve un costruttore standard senza parametri, che chiama
super()e lascia gli altri campi al valore di default.
In Triangolo il costruttore standard dà quindi errore, perché Figura ha solo Figura(String descrizione). Va scritto un costruttore con la descrizione e i tre lati.
L'ambiente di sviluppo (Eclipse) genera costruttori e toString a partire dai campi scelti. Per il toString di Rettangolo vanno selezionati anche i campi ereditati, altrimenti la descrizione non compare. Il codice generato risparmia scrittura ripetitiva, ma bisogna saperlo leggere: non sempre è quello voluto, come si vede in Quadrato.
Metodi stub
L'area del triangolo richiede la formula di Erone, che qui non si implementa. Un metodo che dichiara un valore di ritorno ma non restituisce nulla non compila, quindi si inserisce un return provvisorio.
public double area() {
return 1000; // stub: da completare
}
Un metodo con l'intestazione ma senza il contenuto vero è uno stub: si può già chiamare. Il valore 1000 è riconoscibile se compare per errore in un risultato. Esistono anche classi stub, con al più qualche metodo vuoto. Gli stub servono per scrivere intanto il codice che usa la classe, oppure per far lavorare in parallelo programmatori o team che dipendono l'uno dalle classi dell'altro.
Dati non validi: controllo a esecuzione
Il costruttore di Triangolo accetta lati 3, 2, 4, con perimetro 9, ma anche 1, 2, 4: compila, si esegue e dà perimetro 7, benché non esista un triangolo con quei lati. Anche i valori negativi sarebbero accettati.
Il compilatore non può controllarlo: i valori possono arrivare da un utente, da un file, da un database o dalla rete, e in generale si conoscono solo a esecuzione. Deve verificarli la classe. Tre lati formano un triangolo se ciascuno, al massimo, è uguale alla somma degli altri due:
Con l'uguaglianza il triangolo è schiacciato, di area zero; con un lato maggiore della somma degli altri due i lati non si congiungono. Il controllo, nel costruttore, segnala la violazione di almeno una delle tre condizioni:
public Triangolo(String descrizione, int lato1, int lato2, int lato3) {
super(descrizione);
if (lato1 > lato2 + lato3 || lato2 > lato1 + lato3
|| lato3 > lato1 + lato2)
throw new RuntimeException("Violazione della proprietà triangolare");
this.lato1 = lato1;
this.lato2 = lato2;
this.lato3 = lato3;
}
throw new RuntimeException(...) solleva un'eccezione: il programma si interrompe e la console mostra il messaggio, la classe e la riga che hanno prodotto l'errore. Un errore deve comparire il prima possibile ed essere evidente. In un programma per utenti, invece, si segnalerebbe il dato errato chiedendolo di nuovo. Le eccezioni si approfondiscono più avanti.
private e incapsulamento
Controllare solo il costruttore non basta. Con i campi accessibili, un triangolo valido diventa non valido con t.lato1 = 1, senza alcun controllo. La verifica può stare dove si modificano i dati o dove si usano, per esempio nel calcolo del perimetro. Per garantire che ogni modifica sia controllata, le modifiche devono passare per metodi della classe, e deve essere impossibile aggirarli. A questo serve private. Il controllo, che serve anche negli altri metodi di modifica, va in un metodo a parte, verifica.
public class Triangolo extends Figura {
private int lato1, lato2, lato3;
public void modificaLato1(int nuovo) {
if (!verifica(nuovo, lato2, lato3))
throw new RuntimeException("Violazione della proprietà triangolare");
lato1 = nuovo;
}
public int getLato1() {
return lato1;
}
private boolean verifica(int a, int b, int c) {
return a <= b + c && b <= a + c && c <= a + b;
}
// costruttore e altri metodi come sopra
}
Un campo o un metodo private ha il nome utilizzabile solo fra la graffa di apertura e quella di chiusura della propria classe. In main, t.lato1 = 1 e anche solo t.lato1 sono errori di compilazione, mentre t.modificaLato1(1) è lecito. Un metodo public, invece, è visibile ai clienti della classe: l'insieme dei membri pubblici è l'interfaccia pubblica, e limitare la visibilità serve all'information hiding.
- La regola riguarda dove si scrive il nome, non chi conosce il dato: il valore resta leggibile con
getLato1().privatenon significa riservato. - Gli errori di accesso sono rilevati solo in compilazione, perché dipendono da dove è scritto il codice.
- Il parametro
lato1del costruttore è una variabile diversa dal campothis.lato1: conta il campo, che si può scrivere solo nella classe. - Anche una sottoclasse di
Triangolonon potrebbe scriverelato1.
Il problema si ripresenta in Quadrato. Il suo costruttore riceve un solo lato e lo passa due volte alla superclasse; il codice generato per costruttore e toString, con base e altezza, va riscritto.
public class Quadrato extends Rettangolo {
public Quadrato(String descrizione, int lato) {
super(descrizione, lato, lato);
}
}
Con base e altezza modificabili dall'esterno, q.altezza = 12 su un quadrato di lato 2 dà una figura di base 2 e altezza 12, con perimetro 28: non è più un quadrato. La soluzione è l'incapsulamento: campi privati, accessibili solo tramite metodi get (lettura) e set (modifica), che possono aggiungere controlli. Dopo averli resi privati, Quadrato legge l'altezza con getAltezza(). Il controllo che mantiene base e altezza uguali richiede le eccezioni e si completa più avanti.
Oggetti non modificabili e creazione con new
Se i lati si assegnano solo nel costruttore e non esiste alcun metodo di modifica, un triangolo si crea con i lati controllati e poi non cambia più. Chi usa la classe può solo crearne un altro. Le stringhe Java funzionano così: non si cambia un carattere, se ne costruisce una nuova.
Ogni new crea un oggetto nuovo e invoca un costruttore. Assegnare un new a una variabile già usata cambia l'oggetto a cui la variabile si riferisce, non quello vecchio. L'assegnamento valuta prima il lato destro:
Insiemi di figure
Tutte le sottoclassi sono figure, quindi in un HashSet di Figura si possono inserire quadrati, rettangoli e triangoli: un oggetto della sottoclasse è anche un oggetto della superclasse.
HashSet<Figura> figure = new HashSet<Figura>();
figure.add(new Quadrato("q", 2));
figure.add(new Rettangolo("r", 4, 5));
figure.add(new Triangolo("t", 3, 2, 4));
Esercizio: calcolare il perimetro totale delle figure dell'insieme. Con una variabile di tipo Figura si usano solo i membri di Figura, che ancora non ha perimetro: la classe va modificata.
Formulario
Gerarchia
extends: tipo particolaresuper(...): prima istruzione- sottoclasse: anche superclasse
Rettangolo: base , altezza
Triangolo: lati
Proprietà triangolare
Accesso
public: visibile ai clientiprivate: scrivibile nella classe- errori: a compilazione
Incapsulamento
- campi
private - accesso con
geteset - i controlli stanno nei metodi
Eccezioni
throw new RuntimeException(...)- mostra messaggio e riga
- errore subito e ben visibile
Oggetti
- ogni
newcrea un oggetto - prima si valuta il lato destro
- senza metodi di modifica: fisso