CodeGym /Corsi /JAVA 25 SELF /Record: sintassi, vantaggi

Record: sintassi, vantaggi

JAVA 25 SELF
Livello 22 , Lezione 0
Disponibile

1. Il problema delle classi DTO: perché ci servono le classi record?

Diciamolo sinceramente: quante volte avete scritto una classe del genere?


public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Point point = (Point) o;
        return x == point.x && y == point.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }

    @Override
    public String toString() {
        return "Point{" + "x=" + x + ", y=" + y + '}';
    }
}

Sembra niente di complicato, ma contate le righe! E ora immaginate di averne 10 così, ciascuna con 5–6 campi. Perfino l’IDE si stanca a generare questo codice ripetitivo. E se decidete di aggiungere un nuovo campo — dovrete modificare costruttore, getter, equals, hashCode, toString... Noia, routine e fonte di errori.

Queste classi si chiamano DTO (Data Transfer Object) o Value Object. Conservano semplicemente dei dati — e basta. Ma a causa del codice ripetitivo sono difficili da mantenere.

Se vi sembra che non sia un problema, aspettate il momento in cui dovrete modificarne 50 in una volta. Allora vi ricorderete delle classi record con particolare affetto!

2. Introduzione ai record: sintassi e magia di Java 16+

Con Java 16 è cambiato tutto. È apparso un nuovo tipo di classe — record. Sono pensati apposta per i casi in cui serve solo conservare un insieme di dati. La sintassi è quasi come una tupla in altri linguaggi.

Come dichiarare un record?


public record Point(int x, int y) { }

E... basta! Avete appena creato una classe immutabile con due campi, un costruttore, i getter, equals, hashCode e toString. Senza inutili lungaggini.

Cosa fa Java «sotto il cofano»?

  • I campi x e y diventano private final.
  • I getter vengono generati automaticamente: int x() e int y().
  • Costruttore: public Point(int x, int y).
  • equals/hashCode: confrontano tutti i campi per valore.
  • toString: restituisce una stringa del tipo "Point[x=1, y=2]".

Si può dire che il record è un «DTO sotto steroidi»: meno codice, più garanzie, meno bug.

Immutabilità (immutability)

Tutti i campi di una classe record sono automaticamente final. Dopo la creazione dell’oggetto non è possibile modificarlo — lo garantisce il compilatore.

Se provate ad aggiungere un setter o rendere un campo non final — il compilatore vi fermerà. Una simile cura per la vostra tranquillità non è così comune!

3. Esempio d’uso di una classe record

Classe tradizionale (molto codice):


public class Client {
    private final String name;
    private final int id;

    public Client(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public String getName() { return name; }
    public int getId() { return id; }

    // equals, hashCode, toString ...
}

Classe record (una riga!):


public record Client(String name, int id) { }

Utilizzo:


public class Main {
    public static void main(String[] args) {
        Client client = new Client("Ivan", 123);
        System.out.println(client.name()); // Ivan
        System.out.println(client.id());   // 123
        System.out.println(client);        // Client[name=Ivan, id=123]
    }
}

Da notare:
- I metodi di accesso ai campi hanno lo stesso nome dei campi: name(), id().
- Non esistono setName() o setId() — l’oggetto non può essere modificato dopo la creazione.

4. Vantaggi delle classi record: meno codice, meno errori

Meno codice — più felicità

Perché scrivere 40 righe se basta una? Le classi record fanno risparmiare tempo e nervi, soprattutto nei progetti grandi, dove ci sono molti DTO e value object.

Immutabilità «per contratto»

  • Le classi record sono sempre final e immutabili.
  • L’oggetto non può essere falsificato né modificato per sbaglio.
  • Niente bug «strani» dovuti a modifiche allo stato dell’oggetto in punti inattesi.
  • Si possono usare in sicurezza nei programmi multithread (se anche tutti i campi sono immutabili).

Generazione automatica di equals/hashCode/toString

Non serve scrivere a mano i metodi di confronto, hash e stampa. È tutto generato automaticamente e nel modo corretto.


Client c1 = new Client("Anna", 42);
Client c2 = new Client("Anna", 42);

System.out.println(c1.equals(c2));                 // true
System.out.println(c1.hashCode() == c2.hashCode()); // true
System.out.println(c1);                             // Client[name=Anna, id=42]

Ideale per collezioni e chiavi

Gli oggetti record si possono usare come chiavi in HashMap, elementi in HashSet ecc. — tutto funzionerà correttamente, perché equals e hashCode considerano tutti i campi.


import java.util.HashMap;
import java.util.Map;

Map<Client, String> clients = new HashMap<>();
clients.put(new Client("Anna", 42), "VIP");

System.out.println(clients.get(new Client("Anna", 42))); // VIP

Descrizione esplicita dei dati

La sintassi di una classe record mostra subito quali dati vengono memorizzati e che l’oggetto è immutabile. Questo rende il codice più chiaro per altri sviluppatori (e per voi tra sei mesi).

5. Tabella: confronto tra una classe normale e una classe record

Classe normale Classe record
Sintassi Molto codice Una riga
Immutabilità Da implementare esplicitamente Garantita dal compilatore
Autogenerazione dei metodi No Sì (equals, hashCode, toString)
Possibilità di aggiungere campi Solo componenti del record
Ereditarietà Può essere estesa Sempre final, non estendibile
Uso nelle collezioni Richiede l’implementazione corretta dei metodi Funziona «out of the box»

6. Evolviamo l’applicazione didattica: esempio con una classe record

Supponiamo che nella vostra app bancaria didattica dobbiate memorizzare le operazioni del conto: data, importo, tipo di operazione (ad esempio «versamento» o «prelievo»).

Prima di Java 16:


public class Transaction {
    private final LocalDate date;
    private final double amount;
    private final String type;

    public Transaction(LocalDate date, double amount, String type) {
        this.date = date;
        this.amount = amount;
        this.type = type;
    }

    public LocalDate getDate() { return date; }
    public double getAmount() { return amount; }
    public String getType() { return type; }

    // equals, hashCode, toString ...
}

Con Java 16 e i record:


import java.time.LocalDate;

public record Transaction(LocalDate date, double amount, String type) { }

Utilizzo:


Transaction t = new Transaction(LocalDate.now(), 100.0, "deposit");
System.out.println(t);         // Transaction[date=2024-06-01, amount=100.0, type=deposit]
System.out.println(t.amount()); // 100.0

7. Visualizzazione: cosa genera un record

Vediamo una classe record «espansa» (approssimativamente ciò che genera il compilatore):


public final class Point extends java.lang.Record {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int x() { return x; }
    public int y() { return y; }

    @Override
    public boolean equals(Object o) { /* confronto per campi */ }

    @Override
    public int hashCode() { /* calcolo sui campi */ }

    @Override
    public String toString() { /* output leggibile */ }
}

In breve: quando usare un record

  • Quando serve un oggetto immutabile con un insieme di dati.
  • Quando servono equals/hashCode/toString «onesti» senza scriverli a mano.
  • Quando create DTO, Value Object, coppie, triple, colore, punto, intervallo, chiave per una collezione ecc.

8. Errori tipici nell’uso delle classi record

Errore n. 1: tentare di aggiungere un setter o modificare un campo dopo la creazione.
Una classe record non consente di modificare i propri campi. Se provate ad aggiungere un metodo tipo setX(int x), il compilatore dirà subito «non si può». Lo stesso — se si tenta di modificare direttamente un campo.

Errore n. 2: tentare di aggiungere un campo non statico.
In una classe record si possono dichiarare solo componenti (i campi indicati tra parentesi dopo il nome del record) e campi statici. Non è permesso aggiungere campi non statici — il compilatore non lo consentirà.

Errore n. 3: usare un record per logica mutabile.
Le classi record non sono destinate a oggetti con stato modificabile. Se vi serve cambiare qualcosa dopo la creazione — usate una classe tradizionale.

Errore n. 4: dimenticare che un record è sempre final.
Una classe record non può essere estesa e non può essere usata come superclasse. Tentare di aggirare questo vincolo porta a un errore di compilazione. Indicazione chiave — non provate a «estendere» un record: è pensato come tipo immutabile e completo.

Errore n. 5: ignorare i metodi autogenerati.
Se sovrascrivete equals, hashCode o toString, fate attenzione — non violate i loro contratti, altrimenti collezioni e confronti non funzioneranno correttamente.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION