W3docs

Java Records

Usa i Java records per creare classi dati immutabili con costruttore, accessori, equals e hashCode generati automaticamente.

Un record è una classe il cui unico compito è trasportare dati. Dichiari i campi una volta nell'intestazione e il compilatore genera il costruttore, gli accessori, equals, hashCode e toString per te. Quello che prima richiedeva 40 righe di getter e codice ripetitivo è ora una singola riga:

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

Questa è l'intera classe. new Point(3, 4), p.x(), p.y(), p.equals(other) e p.toString() funzionano tutti esattamente come ti aspetteresti.

I record sono stati definitivamente introdotti in Java 16 (dopo le anteprime nelle versioni 14 e 15). Questa pagina illustra cosa genera il compilatore, come validare e personalizzare un record, le regole che i record impongono e come i record si combinano con il pattern matching.

Perché esistono i record

Prima dei record, ogni "classe dati" necessitava di un campo private final per ogni componente, un costruttore che li assegnasse, un getter per ogni campo, equals e hashCode basati sui valori, e un toString. La maggior parte di quel codice era meccanica ed era facile sbagliare in modo sottile (dimenticare un campo in equals, restituire il campo sbagliato da un getter, copiare un errore di digitazione). I record condensano tutto questo in un'intestazione, eliminando sia la scrittura che i bug.

Componenti e accessori

Gli argomenti nell'intestazione si chiamano componenti. Ognuno diventa:

  • Un campo private final con lo stesso nome.
  • Un metodo accessore con lo stesso nome (non getX() — solo x()).
Point p = new Point(3, 4);
System.out.println(p.x() + ", " + p.y());   // 3, 4

I nomi corrispondono perché i record servono a esporre i dati in modo diretto. Non c'è il prefisso get perché non c'è nulla da nascondere.

equals, hashCode e toString generati

Due record sono uguali se e solo se sono dello stesso tipo di record e ogni componente è uguale:

Point a = new Point(3, 4);
Point b = new Point(3, 4);
System.out.println(a.equals(b));  // true
System.out.println(a);            // Point[x=3, y=4]

hashCode combina tutti i componenti, quindi i record funzionano correttamente come chiavi in HashMap e HashSet senza alcuno sforzo aggiuntivo.

Costruttore compatto

Ogni record ha un costruttore canonico — quello i cui parametri corrispondono ai componenti nell'ordine. Per impostazione predefinita il compilatore lo scrive per te (assegna semplicemente ogni parametro al suo campo). Quando hai bisogno di validazione o normalizzazione, non devi ripetere quelle assegnazioni: scrivi un costruttore compatto, che non ha lista di parametri e viene eseguito prima delle assegnazioni implicite ai campi:

public record Range(int low, int high) {
  public Range {
    if (low > high)
      throw new IllegalArgumentException("low > high");
  }
}

Puoi anche riassegnare le variabili parametro all'interno del costruttore compatto — i valori finali sono quelli che vengono assegnati ai campi:

public record Name(String first, String last) {
  public Name {
    first = first.strip();
    last  = last.strip();
  }
}

Aggiungere metodi

I record possono avere qualsiasi metodo che scriveresti normalmente — semplicemente non possono avere campi di istanza aggiuntivi (tutto ciò che sostiene il record deve essere un componente):

public record Point(int x, int y) {
  public double distanceTo(Point other) {
    int dx = x - other.x;
    int dy = y - other.y;
    return Math.sqrt(dx * dx + dy * dy);
  }
}

I campi statici e i metodi statici sono ammessi. I record possono anche implementare interfacce.

Cosa i record non possono fare

  • Nessuna ereditarietà. Un record estende implicitamente java.lang.Record ed è final — non puoi estendere un record e un record non può estendere un'altra classe.
  • Nessuno stato mutabile. Tutti i componenti sono final. Se vuoi la mutabilità, usa una classe normale.
  • Nessun campo di istanza al di fuori dei componenti. Non puoi aggiungere di nascosto un private int cache; extra.

Queste restrizioni sono il punto centrale. Un record promette "sono solo i miei componenti, nient'altro". È questa promessa che rende sicura la generazione automatica di equals, hashCode e la serializzazione.

Quando usarli

I record sono adatti quando altrimenti scriveresti una piccola classe dati immutabile — DTO, oggetti di configurazione, tipi di ritorno che raggruppano alcuni valori, tuple in switch con pattern matching. Non sono adatti quando il tipo ha identità, possiede stato mutabile o è la radice di una gerarchia.

Un esempio pratico

java— editable, runs on the server

Record nel pattern matching

Poiché i componenti di un record sono pubblici e ordinati, il compilatore sa anche come scomporre un record. Un pattern record associa ciascun componente a una variabile in un solo passaggio, rendendo i record la forma naturale per i dati su cui si esegue switch:

sealed interface Shape permits Circle, Rect {}
record Point(int x, int y) {}
record Circle(Point center, double r) implements Shape {}
record Rect(Point a, Point b) implements Shape {}

static String describe(Shape s) {
  return switch (s) {
    case Circle(Point c, double r) -> "circle r=" + r + " at " + c.x() + "," + c.y();
    case Rect(Point a, Point b)    -> "rect " + a + " to " + b;
  };
}

Il pattern Circle(Point c, double r) verifica che s sia un Circle ed estrae i suoi componenti in una singola espressione. Vedi Java pattern matching per la funzionalità completa, inclusi i pattern annidati.

Cosa c'è dopo

I record vincolano una classe a un insieme fisso di dati. Il prossimo capitolo introduce le sealed class, che vincolano una gerarchia a un insieme fisso di sottotipi — il tassello mancante per modellare famiglie simili ai tipi di dati algebrici chiusi in Java. Continua con Java sealed classes.

Pratica

Pratica
Cosa genera il compilatore quando dichiari `record Point(int x, int y) {}`?
Cosa genera il compilatore quando dichiari `record Point(int x, int y) {}`?
Was this page helpful?