1. Estensione della classe record: metodi aggiuntivi
Si possono aggiungere metodi in un record?
Certo! Un Record è come un appartamento già ristrutturato: muri e pavimento sono pronti, non si possono cambiare, ma nessuno vieta di disporre i mobili come si vuole. All’interno di un record si possono dichiarare metodi normali, metodi statici e persino conservare costanti. Questo significa che la logica di business non è obbligatorio spostarla in classi «di utilità» — la si può integrare con cura direttamente nel record.
Esempio: metodo di calcolo della distanza tra punti
Supponiamo di avere un record per un punto sul piano:
public record Point(int x, int y) {
// Metodo aggiuntivo
public double distanceTo(Point other) {
int dx = this.x - other.x;
int dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
Ora si può fare così:
Point p1 = new Point(0, 0);
Point p2 = new Point(3, 4);
System.out.println(p1.distanceTo(p2)); // 5.0
Come si vede, la classe record si può «arricchire» con metodi propri — ed è molto comodo!
Esempio: metodo statico
public record Rectangle(int width, int height) {
public int area() {
return width * height;
}
public static Rectangle square(int size) {
return new Rectangle(size, size);
}
}
Ora si può creare un «quadrato» con una sola chiamata:
Rectangle r = Rectangle.square(5);
System.out.println(r.area()); // 25
2. Costruttore compatto e validazione dei dati
A cosa serve il costruttore «compatto»?
Il costruttore canonico del record viene creato automaticamente e assegna i parametri ai campi. Ma a volte si vuole aggiungere un controllo degli input (per esempio, vietare coordinate negative).
In una classe normale scriveremmo:
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
if (x < 0 || y < 0) throw new IllegalArgumentException();
this.x = x;
this.y = y;
}
// ...
}
In una classe record si può dichiarare un costruttore compatto — senza ripetere la lista dei parametri e senza l’assegnazione esplicita dei campi (lo fa il compilatore per noi).
Sintassi del costruttore compatto
public record Point(int x, int y) {
public Point {
if (x < 0 || y < 0) {
throw new IllegalArgumentException("Coordinates must be non-negative");
}
// Non serve scrivere: this.x = x; this.y = y;
}
}
- I parametri del costruttore corrispondono automaticamente ai componenti del record.
- L’assegnazione this.x = x e this.y = y è effettuata dal compilatore automaticamente dopo l’esecuzione del corpo del costruttore (o dopo l’uscita riuscita da esso).
- Se viene lanciata un’eccezione, l’oggetto non verrà creato.
Esempio con verifica
Point p1 = new Point(3, 5); // OK
Point p2 = new Point(-1, 2); // Genererà IllegalArgumentException!
Si può dichiarare un costruttore «normale»?
Sì, si può! Se serve creare un costruttore con un’altra lista di parametri o logica aggiuntiva, dichiaratelo esplicitamente:
public record Range(int from, int to) {
public Range(int size) {
this(0, size); // chiama il costruttore principale
}
}
3. Limitazioni delle classi record
A differenza delle classi normali, le classi record hanno una serie di limitazioni. È importante ricordarlo per non sorprendersi degli errori di compilazione.
Solo componenti — nessun campo non statico aggiuntivo
In una classe record non si possono dichiarare nuovi campi non statici:
public record Person(String name, int age) {
// int id; // Errore di compilazione! Non è consentito aggiungere campi non statici.
}
Si possono dichiarare campi e metodi statici:
public record Person(String name, int age) {
public static final String SPECIES = "Homo sapiens";
}
I record sono sempre final
Una classe Record non può essere classe genitore (non si può ereditarne) e non può estendere esplicitamente un’altra classe (tranne l’ereditarietà implicita da java.lang.Record). Ciò significa che una classe record è sempre una struttura «finale».
public record User(String login) { }
// public class Admin extends User {} // Errore: non si può estendere un record!
Si possono implementare interfacce
Una classe Record può implementare interfacce:
public interface Printable {
void print();
}
public record Invoice(int amount) implements Printable {
@Override
public void print() {
System.out.println("Importo: " + amount);
}
}
4. Esempi: record estesi in casi reali
Vediamo alcuni esempi pratici in cui metodi aggiuntivi e costruttori compatti rendono la classe record davvero utile.
Record con un metodo di calcolo
public record Circle(double x, double y, double radius) {
public double area() {
return Math.PI * radius * radius;
}
public double distanceTo(Circle other) {
double dx = x - other.x;
double dy = y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
Record con validazione
public record Email(String value) {
public Email {
if (value == null || !value.contains("@")) {
throw new IllegalArgumentException("Email non valida: " + value);
}
}
}
Ora non si può creare un'email non valida:
Email e1 = new Email("test@example.com"); // OK
Email e2 = new Email("not-an-email"); // Genererà IllegalArgumentException
Record con metodi statici aggiuntivi
public record Temperature(double celsius) {
public static Temperature fromFahrenheit(double fahrenheit) {
return new Temperature((fahrenheit - 32) * 5 / 9);
}
public double toFahrenheit() {
return celsius * 9 / 5 + 32;
}
}
Utilizzo:
Temperature t = Temperature.fromFahrenheit(98.6);
System.out.println(t.celsius()); // 37.0
System.out.println(t.toFahrenheit()); // 98.6
5. Costruttore compatto: sfumature e limitazioni
Quando usare il costruttore compatto?
- Se serve verificare la correttezza dei dati (validazione).
- Se è necessario modificare i valori prima di memorizzarli (per esempio, arrotondare un numero o portare una stringa in maiuscolo).
- Se si vuole evitare di duplicare l’elenco dei parametri.
Caratteristiche di funzionamento
- Nel costruttore compatto non si possono assegnare esplicitamente valori ai componenti (this.x = ...) — questo genererà un errore di compilazione, perché il compilatore esegue l’assegnazione dopo l’esecuzione del corpo del costruttore.
- Nel costruttore compatto non si possono cambiare i nomi dei parametri — corrispondono sempre ai nomi dei componenti del record.
Esempio: arrotondamento automatico
public record Money(double amount) {
public Money {
amount = Math.round(amount * 100) / 100.0; // Arrotondiamo ai centesimi
}
}
6. Pratica: sviluppiamo un’applicazione didattica
Supponiamo di effettuare operazioni bancarie in un’applicazione didattica. Abbiamo una classe record Transaction, che memorizza l’importo, il mittente e il destinatario.
public record Transaction(String from, String to, double amount) {
public Transaction {
if (amount <= 0) throw new IllegalArgumentException("L'importo deve essere positivo");
if (from == null || to == null) throw new IllegalArgumentException("I campi non possono essere null");
}
public String description() {
return String.format("Trasferimento %.2f da %s a %s", amount, from, to);
}
}
Utilizzo:
Transaction t = new Transaction("Alice", "Bob", 150.0);
System.out.println(t.description()); // Trasferimento 150.00 da Alice a Bob
Un tentativo di creare una transazione non valida provocherà un errore:
Transaction t2 = new Transaction("Alice", "Bob", -10.0); // IllegalArgumentException
Tabella: cosa si può e cosa non si può in una classe record
| Consentito in una classe record | Vietato in una classe record |
|---|---|
| Metodi normali | Nuovi campi non statici |
| Metodi e campi statici | Ereditare da altre classi |
| Implementare interfacce | Essere superclasse per altri |
| Costruttori compatti e normali | Modificare i componenti dopo la creazione |
| Sovrascrivere metodi | Usare setter |
7. Errori tipici nel lavoro con le classi record con corpo non standard
Errore № 1: tentativo di aggiungere un campo non statico.
I principianti spesso cercano di aggiungere a una classe record «un altro campo per la logica interna» — per esempio, un contatore o una cache. Non funzionerà: il compilatore restituirà subito un errore. Se avete bisogno di conservare stato aggiuntivo, molto probabilmente la classe record non è la scelta giusta.
Errore № 2: dimenticata la validazione nel costruttore compatto.
Se volete che l’oggetto sia sempre valido, eseguite i controlli nel costruttore compatto. Non fate affidamento sul fatto che «l’utente non inserirà sciocchezze».
Errore № 3: tentativo di modificare un componente dopo la creazione.
I campi della classe record sono final — non si possono modificare né direttamente, né tramite metodi. Se vi serve una struttura mutabile, usate una classe normale.
Errore № 4: duplicazione della logica nei metodi e nel costruttore.
A volte si tenta di duplicare la logica di controllo e quella di calcolo sia nei metodi sia nel costruttore. Meglio fare tutta la validazione nel costruttore e lasciare i metodi alla «pura» logica di business.
Errore № 5: dimenticate le limitazioni dell’ereditarietà.
Una classe Record è sempre final — non si può crearne un sottotipo. Se state progettando una gerarchia in cui servono sottoclassi, usate classi normali.
GO TO FULL VERSION