CodeGym /Corsi /JAVA 25 SELF /Differenze tra record e class, limitazioni dei record

Differenze tra record e class, limitazioni dei record

JAVA 25 SELF
Livello 22 , Lezione 4
Disponibile

1. Confronto tra record e class: quali sono le principali differenze?

In Java abbiamo due modi principali per definire i nostri tipi di dati: tramite le classi tradizionali (class) e tramite le classi record (record). A prima vista, entrambe le opzioni permettono di memorizzare ed elaborare dati. Ma, se andiamo un po’ più a fondo, le differenze sono più numerose di quanto sembri!

Tabella delle differenze: class vs record

Caratteristica Classe tradizionale (class) Classe record (record)
Mutabilità Qualsiasi: è possibile avere campi final o meno Immutabile: tutti i campi sono final
Ereditarietà È possibile estendere (extends), non è final di default Sempre final, non può essere una superclasse
Campi Qualsiasi: statici, non statici, final o non-final, di qualsiasi tipo Solo componenti del record (private final), più campi statici
Getter/setter Li scriviamo noi (o li generiamo con Lombok) I getter vengono creati automaticamente (il nome del campo coincide con il nome del metodo), non ci sono setter
equals/hashCode/toString Di solito si scrivono a mano/si generano (equals, hashCode, toString) Generati automaticamente per tutti i componenti
Costruttori Qualsiasi, quanti se ne vuole Uno principale (per tutti i componenti), è possibile aggiungere un costruttore compatto
Interfacce Possono essere implementate Possono essere implementate
Metodi aggiuntivi Qualsiasi Si possono aggiungere, ma solo metodi (non campi)
Uso nelle collezioni Possibile, ma serve implementare correttamente equals/hashCode Ideale come chiavi/valori, tutto è già implementato

Esempio illustrativo

Classe tradizionale:


public class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public boolean equals(Object o) { /* ... */ }
    @Override
    public int hashCode() { /* ... */ }
    @Override
    public String toString() { /* ... */ }
}

Classe record:


public record Person(String name, int age) { }

Fatto! Una sola riga di codice — e otteniamo lo stesso risultato (e persino meglio). E nessun rischio di dimenticare di implementare qualcosa di importante.

2. Limitazioni dei record

Le classi record non sono semplicemente «sintassi breve», ma una vera e propria concezione con regole rigorose. Vediamole più in dettaglio.

I record sono sempre final

Una classe record per definizione è sempre final. Questo significa che non puoi creare una sottoclasse di un record:


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

// public class ColoredPoint extends Point { } // Errore di compilazione!

Se hai bisogno di estendere il comportamento, usa classi tradizionali o la composizione (incapsula il record in una classe).

Un record non può essere una superclasse

Un record non può essere padre di altre classi, è sempre final. È logico: se fosse possibile, qualcuno potrebbe aggiungere un campo mutabile — e l’intera idea dei «dati immutabili» crollerebbe.

Solo campi final (componenti)

Tutti i componenti del record sono dichiarati nell’intestazione e sono per impostazione predefinita private final. Non puoi aggiungere campi non statici nel corpo del record:


public record User(String login, String email) {
    // int counter; // Errore! Non è consentito aggiungere campi non statici
    static int totalUsers = 0; // Consentito, è un campo statico
}

Nessun setter

Una classe record non può avere setter per i componenti. Qualsiasi tentativo di aggiungere un metodo come setX(int x) sarebbe privo di senso: non potrai modificare il valore del campo dopo la creazione dell’oggetto.


public record Point(int x, int y) {
    // public void setX(int x) { this.x = x; } // Errore: impossibile modificare un campo final
}

Nessun costruttore vuoto

Una classe record ha sempre e solo il costruttore principale, che accetta i valori per tutti i componenti. Non è possibile creare un record senza specificare tutti i dati:


Point p = new Point(1, 2);  // OK
// Point p = new Point();   // Errore: nessun costruttore senza parametri

Nessun inizializzatore non statico

Una classe record non può contenere inizializzatori non statici (quelli scritti tra parentesi graffe fuori dai metodi):


public record User(String login) {
    // { /* ... */ } // Errore: gli inizializzatori non statici sono vietati
}

Limitazioni sull’ereditarietà

Una classe record non può estendere esplicitamente un’altra classe (tranne java.lang.Record, che è la classe base nascosta per tutti i record). Ma l’implementazione delle interfacce è consentita!


public interface Printable {
    void print();
}

public record Book(String title) implements Printable {
    @Override
    public void print() {
        System.out.println("Stampiamo il libro: " + title);
    }
}

Non adatto a una logica di business complessa

Un record riguarda i dati, non il comportamento. Se il tuo oggetto ha una logica complessa, uno stato mutabile, un «ciclo di vita» o molte dipendenze, il record non aiuterà. Meglio usare una classe tradizionale.

3. Quando conviene usare i record?

  • DTO (Data Transfer Object): per trasferire dati immutabili tra livelli dell’applicazione, servizi, microservizi o controller REST (ad esempio nelle risposte JSON).
  • Value Object: oggetti che sono definiti unicamente dai loro valori.
  • Chiavi e valori nelle collezioni: quando è importante una corretta implementazione di equals e hashCode (ad esempio per l’uso in HashMap o Set).
  • Risultati di calcoli: quando occorre restituire dal metodo più valori contemporaneamente (ad esempio, record Pair<T, U>(T first, U second)).

Esempio: DTO per un controller REST


public record UserDto(String login, String email) { }

Ora si può restituire tranquillamente un oggetto di questo tipo dal controller, senza temere che qualcuno ne modifichi i campi.

Esempio: Chiave per HashMap


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

Map<Point, String> pointNames = new HashMap<>();
pointNames.put(new Point(1, 2), "A");
pointNames.put(new Point(3, 4), "B");

// Tutto funziona correttamente: equals e hashCode sono già implementati!

4. Quando NON conviene usare i record

  • Stato mutabile: se almeno un campo deve poter cambiare dopo la creazione dell’oggetto.
  • Logica complessa: se l’oggetto ha un comportamento complesso, molti metodi, oggetti annidati con stato mutabile.
  • Ereditarietà: se serve una gerarchia di classi, classi base astratte, override dei metodi.
  • Entità di business: ad esempio oggetti che vivono nel database e hanno un identificatore univoco.

Esempio: quando serve una classe tradizionale


public class Account {
    private String id;
    private int balance;

    public Account(String id, int balance) {
        this.id = id;
        this.balance = balance;
    }

    public void deposit(int amount) { balance += amount; }
    public void withdraw(int amount) { balance -= amount; }
    // getters, setters, equals, hashCode, toString...
}

Qui è evidente che lo stato dell’oggetto cambia — un record non è adatto.

5. Esempi pratici: scegliere tra record e class

Esempio 1: record — la scelta ideale


public record Rectangle(int width, int height) {
    public int area() {
        return width * height;
    }
}
  • Il rettangolo è definito solo da larghezza e altezza.
  • Non c’è necessità di modificare questi valori dopo la creazione.
  • Si può aggiungere un metodo utile come area().
  • Il resto lo farà Java per voi.

Esempio 2: class — l’opzione migliore


public class MutableRectangle {
    private int width;
    private int height;

    public MutableRectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }

    public void setWidth(int width) { this.width = width; }
    public void setHeight(int height) { this.height = height; }

    public int area() { return width * height; }
}

Serve modificare le dimensioni del rettangolo dopo la creazione? Usa una classe tradizionale.

6. Errori tipici nell’uso dei record

Errore n. 1: tentativo di aggiungere un campo non statico.
Una classe record non consente di dichiarare campi non statici al di fuori dell’elenco dei componenti. Se ci si prova, il compilatore segnalerà un errore. Ad esempio:


public record City(String name) {
    // int population; // Errore!
}

Errore n. 2: voler aggiungere un setter.
Un record non supporta setter per i componenti. Qualsiasi tentativo di modificare il valore di un campo dopo la creazione dell’oggetto comporta un errore di compilazione.

Errore n. 3: tentativo di ereditare da un record o di far ereditare un record.
Un record è sempre final. Non si può ereditare da un record e un record non può estendere un’altra classe (tranne il java.lang.Record nascosto).

Errore n. 4: utilizzo dei record per oggetti mutabili.
Se prevedi di modificare lo stato dell’oggetto dopo la creazione, il record non fa per te! Usa una classe tradizionale.

Errore n. 5: dimenticare le limitazioni del costruttore.
Una classe record deve avere un costruttore che accetti i valori per tutti i componenti. Non esiste un costruttore senza parametri!

1
Compito
JAVA 25 SELF, livello 22, lezione 4
Bloccato
Catalogazione della biblioteca di casa 📚
Catalogazione della biblioteca di casa 📚
1
Compito
JAVA 25 SELF, livello 22, lezione 4
Bloccato
Verifica della corrispondenza delle coordinate sulla mappa 🗺️
Verifica della corrispondenza delle coordinate sulla mappa 🗺️
1
Compito
JAVA 25 SELF, livello 22, lezione 4
Bloccato
Stampa del contenuto del documento 📄
Stampa del contenuto del documento 📄
1
Compito
JAVA 25 SELF, livello 22, lezione 4
Bloccato
Confronto tra profili utente mutabili e immutabili 🧑‍🤝‍🧑
Confronto tra profili utente mutabili e immutabili 🧑‍🤝‍🧑
1
Sondaggio/quiz
Classi record, livello 22, lezione 4
Non disponibile
Classi record
Classi record
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION