L’incapsulamento in Java protegge lo stato di un oggetto e mette a disposizione operazioni controllate. Non consiste soltanto nello scrivere private e generare automaticamente getter e setter: il punto è garantire che l’oggetto rimanga sempre in uno stato valido.
Il problema: campi pubblici e dati incoerenti
class Serbatoio {
public int capacita;
public int livello;
}
// Dal codice esterno:
Serbatoio s = new Serbatoio();
s.capacita = 50;
s.livello = -10; // Valore assurdo, ma il compilatore lo consente
L’esempio è volutamente sbagliato: i campi pubblici permettono a qualsiasi codice di assegnare un livello negativo o superiore alla capacità. Il compilatore Java non conosce automaticamente le regole fisiche del serbatoio.
Information hiding e invarianti di classe
L’information hiding consiste nel nascondere i dettagli della rappresentazione interna. Il codice esterno usa l’interfaccia pubblica senza dipendere da come sono memorizzati i dati. Un’invariante è una condizione che ogni stato valido dell’oggetto deve rispettare. Per il serbatoio scegliamo capacita > 0 e 0 <= livello <= capacita.
Esempio completo: Serbatoio incapsulato
class Serbatoio {
private final int capacita;
private int livello;
public Serbatoio(int capacita, int livelloIniziale) {
if (capacita <= 0) {
throw new IllegalArgumentException("Capacita non valida");
}
if (livelloIniziale < 0 || livelloIniziale > capacita) {
throw new IllegalArgumentException("Livello iniziale non valido");
}
this.capacita = capacita;
this.livello = livelloIniziale;
}
public void rifornisci(int litri) {
if (litri <= 0 || litri > capacita - livello) {
throw new IllegalArgumentException("Rifornimento non valido");
}
livello += litri;
}
public void preleva(int litri) {
if (litri <= 0 || litri > livello) {
throw new IllegalArgumentException("Prelievo non valido");
}
livello -= litri;
}
public int getLivello() {
return livello;
}
public int getCapacita() {
return capacita;
}
}
public class Main {
public static void main(String[] args) {
Serbatoio s = new Serbatoio(50, 20);
s.rifornisci(10); // Livello: 30
s.preleva(8); // Livello: 22
System.out.println(s.getLivello()); // 22
}
}
La classe è responsabile di verificare le condizioni prima di cambiare il livello. Gli attributi non possono essere modificati direttamente da Main. Il campo capacita è anche final: dopo la costruzione non può essere riassegnato.
Tabella di traccia e stato valido
| Operazione | Livello prima | Esito | Livello dopo |
|---|---|---|---|
new Serbatoio(50,20) | – | Costruzione valida | 20 |
rifornisci(10) | 20 | Accettata | 30 |
preleva(8) | 30 | Accettata | 22 |
preleva(30) | 22 | Rifiutata: litri insufficienti | 22 |
rifornisci(40) | 22 | Rifiutata: supera la capacità | 22 |
Nel programma Java riportato sopra le operazioni rifiutate lanciano IllegalArgumentException. Se l’eccezione non viene gestita, il flusso normale si interrompe. Se viene intercettata dal codice client, il serbatoio continua a conservare il valore precedente.
Getter e setter: quali servono davvero?
Un getter restituisce informazioni sull’oggetto, ad esempio getLivello(). Un setter chiede di modificare un campo, ma deve rispettare i vincoli dell’oggetto. In questa classe non abbiamo creato setLivello(): il livello cambia solo tramite rifornisci() e preleva(), che esprimono operazioni significative del problema.
Modificatori di accesso in Java
| Modificatore | Accesso essenziale |
|---|---|
public | Da tutto il codice che può accedere alla classe |
private | Nella classe che dichiara il membro (incluse le regole Java per classi annidate) |
| Nessun modificatore (package-private) | Dalle classi dello stesso package |
protected | Dallo stesso package e dalle sottoclassi con le regole previste da Java |
Esercizi con soluzione
- Controllo del costruttore: cosa succede con
new Serbatoio(40, 60)? - Precondizione: un serbatoio con capacità 60 e livello 55 può ricevere 6 litri?
- Progettazione: aggiungi un metodo
isVuoto()che restituisca unbooleansenza cambiare il livello. - Errore di progetto: spiega perché rendere pubblico
livellorende inefficaci le verifiche nei metodi. - Estensione: definisci una classe
Magazzinoconquantitaprivata e metodicaricaescarica.
Soluzioni sintetiche
Nel primo esercizio il costruttore rifiuta lo stato perché 60 supera la capacità 40. Nel secondo, il rifornimento va rifiutato: 55 + 6 supera 60. Il metodo isVuoto() può restituire livello == 0. Il campo pubblico vanificherebbe i controlli perché il client potrebbe assegnare valori non validi senza chiamare i metodi.
Nel percorso OOP: Torna all’indice delle lezioni Java · Ripassa classi e oggetti.


