Getter e Setter in Java
Esponi i campi privati in modo sicuro in Java con i metodi getter e setter e aggiungi la validazione nei setter.
Un getter è un metodo che restituisce il valore di un campo; un setter è un metodo che scrive su di esso. Insieme rappresentano il modo standard per fornire un accesso controllato a un campo privato. Il capitolo precedente sull'incapsulamento ha spiegato il perché — questo capitolo riguarda il come, incluse le convenzioni di denominazione, i pattern di validazione e quando è opportuno saltarli del tutto.
Il pattern di base
Un campo, un getter e un setter:
public class User {
private String email;
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}Per convenzione:
- Il getter è denominato
get<FieldName>per tutto tranne i booleani. - Per un campo
boolean, il getter è denominatois<FieldName>(isActive,hasPermission). - Il setter è denominato
set<FieldName>e accetta un parametro del tipo del campo. - Entrambi sono
publica meno che non ci sia una ragione diversa.
Queste regole corrispondono anche alla convenzione JavaBeans — molti strumenti (librerie di serializzazione, template engine, refactoring degli IDE) si basano su di esse e semplicemente non riusciranno a vedere i campi i cui accessor hanno nomi diversi.
Validazione nei setter
Un setter è l'unica occasione per rifiutare input errati prima che il campo venga scritto:
public class User {
private String email;
public void setEmail(String email) {
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("not a valid email: " + email);
}
this.email = email;
}
}Un setter vuoto che non fa altro che assegnare non è molto meglio di un campo pubblico. Lo scopo di inserire un setter tra i chiamanti e il campo è poter aggiungere un controllo. Se ti ritrovi a scrivere decine di setter che leggono tutti come this.x = x;, chiediti se la classe debba essere mutabile — un oggetto con soli getter impostato nel costruttore (una classe immutabile o un record) è spesso una scelta migliore.
Getter calcolati e derivati
Un getter non deve necessariamente corrispondere uno a uno a un campo. Può calcolare il valore di ritorno:
public class Rectangle {
private final double width, height;
public Rectangle(double w, double h) { this.width = w; this.height = h; }
public double getWidth() { return width; }
public double getHeight() { return height; }
public double getArea() { return width * height; } // derived
}Per un chiamante, getArea() appare esattamente come getWidth() — sono entrambi semplicemente metodi che restituiscono un double. Il fatto che uno legga un valore memorizzato e l'altro lo calcoli è un dettaglio implementativo che puoi modificare senza che nessuno se ne accorga.
Getter in sola lettura
Un getter senza setter espone un campo in lettura ma lo blocca in scrittura:
public class Order {
private final long id;
private final long createdAt;
public Order(long id, long createdAt) {
this.id = id;
this.createdAt = createdAt;
}
public long getId() { return id; }
public long getCreatedAt() { return createdAt; }
}Il codice esterno può chiedere "qual è l'ID?" ma non può modificarlo. Ecco come funzionano in pratica i campi immutabili dopo la costruzione.
Denominazione dei booleani: is, has, should
I getter booleani si leggono più naturalmente con un prefisso is/has/can/should:
public boolean isActive() { return active; }
public boolean hasPermission() { return permission != null; }
public boolean canRetry() { return retries < maxRetries; }Combinato con il nome del campo, il sito di chiamata si legge come una frase in inglese: if (user.isActive()) { ... }. Il setter per questi è semplicemente setActive(boolean), omettendo is.
Un'avvertenza: gli strumenti JavaBeans derivano il nome della proprietà dall'accessor, quindi isActive() e getActive() corrispondono entrambi alla proprietà active — ma gli strumenti in genere cercano gli accessor con prefisso is solo per il tipo primitivo boolean. Per un campo Boolean boxed, usa getActive() per restare individuabile.
Copie difensive (ancora)
Se un getter dovesse restituire un oggetto interno mutabile, restituisci una copia o una vista non modificabile:
private final List<String> tags = new ArrayList<>();
public List<String> getTags() {
return List.copyOf(tags);
}Allo stesso modo in ingresso per un setter:
public void setTags(List<String> tags) {
this.tags.clear();
this.tags.addAll(tags); // copy in
}Senza queste copie, il chiamante potrebbe mutare la lista interna tags e aggirare tutto ciò che la classe fa.
Java moderno: accessor più brevi
Il codice Java più recente (specialmente nelle librerie non vincolate agli strumenti JavaBeans) spesso omette il prefisso get per simmetria con il modo in cui vengono denominati gli accessor dei record:
public class Point {
private final int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
public int x() { return x; }
public int y() { return y; }
}Se non sei vincolato da JavaBeans (niente Hibernate, niente impostazioni predefinite di Jackson, niente JSP), questo stile va bene — e corrisponde a ciò che record Point(int x, int y) {} genererebbe automaticamente. Scegli uno stile per codebase e applicalo in modo coerente. Per il quadro completo dell'approccio in stile record, consulta modern records.
Non generarli per riflesso
Gli IDE moderni possono generare felicemente un getter e un setter per ogni campo con una sola pressione di tasto. Resisti all'impulso. Ogni coppia generata è una decisione di design che stai prendendo:
- Un getter espone un campo — vuoi davvero che i chiamanti lo leggano?
- Un setter espone un percorso di scrittura — vuoi davvero che i chiamanti lo modifichino?
- Se entrambe le risposte sono incondizionatamente sì, perché il campo è privato?
La risposta giusta è spesso "nessuno dei due" — sostituisci setBalance(int) con deposit(int) e withdraw(int) che esprimono le operazioni reali.
Un esempio completo
Cosa c'è dopo
Questo conclude le basi di una singola classe — come dichiararla, come controllarne i membri, come esporre solo lo stretto necessario all'esterno. Il prossimo capitolo introduce la seconda grande idea OOP: l'ereditarietà, in cui una classe si costruisce su un'altra invece di partire da zero. Continua con l'ereditarietà in Java.