In questa lezione impari a
- comprendere il concetto di incapsulamento
- imparare i modificatori di accesso (public, private, protected)
- creare getter e setter
- capire i vantaggi dell'information hiding
- applicare la validazione nei setter
Cos'è l'incapsulamento?
L'incapsulamento è uno dei pilastri fondamentali della OOP.
Significa nascondere i dettagli interni di un oggetto e fornire un'interfaccia controllata per accedervi.
È come avere una cassaforte: dall'esterno non si vede cosa c'è dentro, e si può accedervi solo in modo controllato.
Problema: accesso diretto
Finora abbiamo sempre acceduto direttamente agli attributi:
public class ContoBancario {
String intestatario;
double saldo;
public ContoBancario(String intestatario, double saldo) {
this.intestatario = intestatario;
this.saldo = saldo;
}
}
// Nel main:
ContoBancario conto = new ContoBancario("Mario", 1000);
conto.saldo = -500; // PROBLEMA! Posso mettere valori non validi!
System.out.println(conto.saldo); // -500
Attenzione: problemi di questo approccio.
- Chiunque può modificare direttamente i dati
- Non possiamo controllare se i valori sono validi
- Difficile fare debug (non sappiamo chi ha modificato cosa)
- Se cambiamo il nome di un attributo, dobbiamo modificare tutto il codice
Modificatori di accesso
Java fornisce tre modificatori principali per controllare l'accesso:
| Modificatore | Accesso da | Uso tipico |
|---|---|---|
public |
Ovunque | Classi, metodi pubblici |
private |
Solo dalla classe stessa | Attributi, metodi interni |
protected |
Dalla classe e sue sottoclassi | Per l'ereditarietà (lo vedremo) |
| (nessuno) | Stesso package | Accesso a livello di package |
Rendere gli attributi private
La best practice è rendere gli attributi private:
public class ContoBancario {
// Attributi PRIVATE - non accessibili dall'esterno
private String intestatario;
private String numeroConto;
private double saldo;
public ContoBancario(String intestatario, String numeroConto, double saldoIniziale) {
this.intestatario = intestatario;
this.numeroConto = numeroConto;
// Controllo che il saldo sia valido
if (saldoIniziale >= 0) {
this.saldo = saldoIniziale;
} else {
this.saldo = 0;
System.out.println("Saldo iniziale non valido. Impostato a 0.");
}
}
// Metodi pubblici per interagire con l'oggetto
public void deposita(double importo) {
if (importo > 0) {
saldo += importo;
System.out.println("Depositati €" + importo);
} else {
System.out.println("Importo non valido!");
}
}
public boolean preleva(double importo) {
if (importo > 0 && saldo >= importo) {
saldo -= importo;
System.out.println("Prelevati €" + importo);
return true;
} else {
System.out.println("Operazione non consentita!");
return false;
}
}
public void stampaSaldo() {
System.out.println("Saldo di " + intestatario + ": €" + saldo);
}
}
public class TestConto {
public static void main(String[] args) {
ContoBancario conto = new ContoBancario("Mario Rossi", "IT001", 1000);
// conto.saldo = -500; // ERRORE! saldo è private!
conto.stampaSaldo();
conto.deposita(500);
conto.preleva(200);
conto.preleva(2000); // Non può prelevare, fondi insufficienti
conto.stampaSaldo();
}
}
Saldo di Mario Rossi: €1000.0
Depositati €500.0
Prelevati €200.0
Operazione non consentita!
Saldo di Mario Rossi: €1300.0
Getter e setter
I getter e setter sono metodi pubblici che permettono di leggere e modificare gli attributi privati in modo controllato.
- Getter: metodo per LEGGERE un valore (get + NomeAttributo)
- Setter: metodo per MODIFICARE un valore (set + NomeAttributo)
Sintassi dei getter e setter
public class Studente {
// Attributi privati
private String nome;
private String cognome;
private int eta;
private double mediaVoti;
// Costruttore
public Studente(String nome, String cognome, int eta, double mediaVoti) {
this.nome = nome;
this.cognome = cognome;
this.eta = eta;
this.mediaVoti = mediaVoti;
}
// GETTER - metodi per leggere i valori
public String getNome() {
return nome;
}
public String getCognome() {
return cognome;
}
public int getEta() {
return eta;
}
public double getMediaVoti() {
return mediaVoti;
}
// SETTER - metodi per modificare i valori CON CONTROLLI
public void setNome(String nome) {
if (nome != null && !nome.isEmpty()) {
this.nome = nome;
} else {
System.out.println("Nome non valido!");
}
}
public void setCognome(String cognome) {
if (cognome != null && !cognome.isEmpty()) {
this.cognome = cognome;
} else {
System.out.println("Cognome non valido!");
}
}
public void setEta(int eta) {
if (eta >= 0 && eta <= 120) {
this.eta = eta;
} else {
System.out.println("Età non valida!");
}
}
public void setMediaVoti(double mediaVoti) {
if (mediaVoti >= 0 && mediaVoti <= 10) {
this.mediaVoti = mediaVoti;
} else {
System.out.println("Media voti non valida!");
}
}
// Metodo per stampare le info
public void stampaInfo() {
System.out.println(nome + " " + cognome +
", " + eta + " anni, media: " + mediaVoti);
}
}
Uso di getter e setter
public class TestStudente {
public static void main(String[] args) {
Studente studente = new Studente("Mario", "Rossi", 17, 7.5);
// Leggere i valori con i GETTER
System.out.println("Nome: " + studente.getNome());
System.out.println("Età: " + studente.getEta());
System.out.println("Media: " + studente.getMediaVoti());
System.out.println("\n--- Modifico i valori ---");
// Modificare i valori con i SETTER
studente.setMediaVoti(8.0); // OK
studente.setEta(18); // OK
// Tentativo con valori non validi
studente.setMediaVoti(15); // Rifiutato!
studente.setEta(-5); // Rifiutato!
System.out.println("\n--- Info finali ---");
studente.stampaInfo();
}
}
Nome: Mario
Età: 17
Media: 7.5
--- Modifico i valori ---
Media voti non valida!
Età non valida!
--- Info finali ---
Mario Rossi, 18 anni, media: 8.0
Vantaggi di getter e setter.
- Validazione: possiamo controllare che i valori siano corretti
- Flessibilità: possiamo cambiare l'implementazione interna senza modificare il codice esterno
- Read-only: possiamo creare attributi in sola lettura (solo getter, no setter)
- Write-only: possiamo creare attributi in sola scrittura (solo setter, no getter)
- Calcoli: un getter può calcolare un valore invece di restituirlo direttamente
Esempio: getter calcolato
public class Persona {
private String nome;
private String cognome;
private int annoNascita;
public Persona(String nome, String cognome, int annoNascita) {
this.nome = nome;
this.cognome = cognome;
this.annoNascita = annoNascita;
}
// Getter normali
public String getNome() {
return nome;
}
public String getCognome() {
return cognome;
}
// Getter CALCOLATO - non esiste un attributo "eta"
public int getEta() {
int annoCorrente = 2025;
return annoCorrente - annoNascita;
}
// Getter che restituisce il nome completo
public String getNomeCompleto() {
return nome + " " + cognome;
}
// Setter
public void setNome(String nome) {
this.nome = nome;
}
public void setCognome(String cognome) {
this.cognome = cognome;
}
}
public class TestPersona {
public static void main(String[] args) {
Persona persona = new Persona("Giulia", "Bianchi", 2007);
System.out.println("Nome completo: " + persona.getNomeCompleto());
System.out.println("Età: " + persona.getEta()); // Calcolata!
}
}
Nome completo: Giulia Bianchi
Età: 18
Esempio completo: classe Auto
public class Auto {
private String marca;
private String modello;
private int anno;
private double velocitaAttuale;
private double velocitaMassima;
private boolean accesa;
public Auto(String marca, String modello, int anno, double velocitaMassima) {
this.marca = marca;
this.modello = modello;
this.anno = anno;
this.velocitaMassima = velocitaMassima;
this.velocitaAttuale = 0;
this.accesa = false;
}
// Getter
public String getMarca() {
return marca;
}
public String getModello() {
return modello;
}
public int getAnno() {
return anno;
}
public double getVelocitaAttuale() {
return velocitaAttuale;
}
public double getVelocitaMassima() {
return velocitaMassima;
}
public boolean isAccesa() { // Per boolean si usa "is" invece di "get"
return accesa;
}
// Setter con validazione
public void setVelocitaMassima(double velocitaMassima) {
if (velocitaMassima > 0 && velocitaMassima <= 500) {
this.velocitaMassima = velocitaMassima;
} else {
System.out.println("Velocità massima non valida!");
}
}
// Metodi pubblici per l'interazione
public void accendi() {
if (!accesa) {
accesa = true;
velocitaAttuale = 0;
System.out.println(marca + " " + modello + " accesa!");
}
}
public void spegni() {
if (accesa && velocitaAttuale == 0) {
accesa = false;
System.out.println("Auto spenta.");
} else if (velocitaAttuale > 0) {
System.out.println("Ferma l'auto prima di spegnerla!");
}
}
public void accelera(double incremento) {
if (!accesa) {
System.out.println("Accendi l'auto prima!");
return;
}
velocitaAttuale += incremento;
if (velocitaAttuale > velocitaMassima) {
velocitaAttuale = velocitaMassima;
}
System.out.println("Velocità: " + velocitaAttuale + " km/h");
}
public void frena(double decremento) {
velocitaAttuale -= decremento;
if (velocitaAttuale < 0) {
velocitaAttuale = 0;
}
System.out.println("Velocità: " + velocitaAttuale + " km/h");
}
}
Esercizi guidati
⭐ Facile
Esercizio 1 – classe Prodotto incapsulato.
Obiettivo: Creare una classe Prodotto con incapsulamento.
Requisiti:
- Attributi privati: nome, prezzo, quantitaDisponibile
- Costruttore
- Getter per tutti gli attributi
- Setter per prezzo (deve essere > 0)
- Metodo
vendi(int quantita)che riduce la disponibilità - Metodo
rifornisci(int quantita)che aumenta la disponibilità
Codice iniziale:
public class Prodotto {
private String nome;
private double prezzo;
private int quantitaDisponibile;
public Prodotto(String nome, double prezzo, int quantitaDisponibile) {
// Completa
}
// Crea getter e setter
public boolean vendi(int quantita) {
// Controlla se c'è abbastanza disponibilità
// Se sì, riduci e restituisci true
// Altrimenti restituisci false
}
public void rifornisci(int quantita) {
// Aumenta la disponibilità
}
}
⭐⭐ Medio
Esercizio 2 – classe DataNascita.
Obiettivo: Creare una classe per gestire date di nascita con validazione.
Requisiti:
- Attributi privati: giorno, mese, anno
- Costruttore che valida la data
- Setter con validazione (giorno 1-31, mese 1-12, anno ragionevole)
- Getter per tutti gli attributi
- Metodo
calcolaEta()che restituisce l'età - Metodo
stampaData()formato "gg/mm/aaaa"
Esercizi da svolgere
⭐⭐ Medio
Esercizio 3 – classe password.
Crea una classe Password con:
- Attributo privato: password (String)
- Setter che accetta solo password con almeno 8 caratteri
- Metodo
verifica(String tentativo)che restituisce true se corrisponde - NO getter per la password (sicurezza!)
⭐⭐ Medio
Esercizio 4 – classe TermostatoTemperatura.
Crea una classe Termostato con:
- Attributi privati: temperaturaAttuale, temperaturaImpostata
- Temperatura impostata deve essere tra 15 e 30 gradi
- Metodi per aumentare/diminuire la temperatura impostata
- Metodo
isRiscaldamentoAttivo()
⭐⭐⭐ Difficile
Esercizio 5 – classe email.
Crea una classe Email con:
- Attributo privato: indirizzo
- Setter che valida che la mail contenga "@" e "."
- Metodo
getDominio()che restituisce la parte dopo la @ - Metodo
getUtente()che restituisce la parte prima della @
Concetti chiave da ricordare
- Incapsulamento = nascondere i dettagli interni e fornire un'interfaccia controllata
- Gli attributi dovrebbero essere
private- I metodi pubblici dovrebbero essere
public- Getter per leggere i valori (get + NomeAttributo)
- Setter per modificare i valori con validazione (set + NomeAttributo)
- Per boolean: usa
isAttributo()invece digetAttributo()- I setter permettono di validare i dati prima di modificarli
Best practices
- Rendi SEMPRE privati gli attributi (salvo rare eccezioni)
- Fornisci getter e setter solo per gli attributi che devono essere accessibili
- Valida SEMPRE i dati nei setter
- Non creare setter per attributi che non dovrebbero essere modificati dall'esterno
- Documenta bene i tuoi metodi pubblici (sono l'interfaccia della classe)