JavaRush /Java Blog /Random-IT /Pausa caffè #128. Guida ai record Java

Pausa caffè #128. Guida ai record Java

Pubblicato nel gruppo Random-IT
Fonte: abhinavpandey.dev In questo tutorial tratteremo le basi dell'utilizzo dei record in Java. I record sono stati introdotti in Java 14 come un modo per rimuovere il codice standard relativo alla creazione di oggetti Value sfruttando al tempo stesso gli oggetti immutabili. Pausa caffè #128.  Guida ai record Java - 1

1. Concetti di base

Prima di entrare nelle voci stesse, diamo un'occhiata al problema che risolvono. Per fare ciò, dovremo ricordare come venivano creati gli oggetti valore prima di Java 14.

1.1. Oggetti di valore

Gli oggetti valore sono parte integrante delle applicazioni Java. Memorizzano i dati che devono essere trasferiti tra i livelli dell'applicazione. Un oggetto valore contiene campi, costruttori e metodi per accedere a tali campi. Di seguito è riportato un esempio di oggetto valore:
public class Contact {
    private final String name;
    private final String email;

    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }
}

1.2. Uguaglianza tra oggetti Valore

Gli oggetti valore possono anche fornire un modo per confrontarli per verificarne l'uguaglianza. Per impostazione predefinita, Java confronta l'uguaglianza degli oggetti confrontando il loro indirizzo di memoria. Tuttavia, in alcuni casi, gli oggetti contenenti gli stessi dati possono essere considerati uguali. Per implementare ciò, possiamo sovrascrivere i metodi equals e .hashCode . Implementiamoli per la classe Contact :
public class Contact {

    // ...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Contact contact = (Contact) o;
        return Object.equals(email, contact.email) &&
                Objects.equals(name, contact.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, email);
    }
}

1.3. Immutabilità degli oggetti valore

Gli oggetti valore devono essere immutabili. Ciò significa che dobbiamo limitare i modi in cui possiamo modificare i campi di un oggetto. Ciò è consigliabile per i seguenti motivi:
  • Per evitare il rischio di modificare accidentalmente il valore del campo.
  • Per garantire che oggetti uguali rimangano gli stessi per tutta la vita.
Poiché la classe Contact è già immutabile, ora:
  1. reso i campi privati ​​e definitivi .
  2. fornito solo un getter per ogni campo (nessun setter ).

1.4. Registrazione di oggetti valore

Spesso abbiamo bisogno di registrare i valori contenuti negli oggetti. Questo viene fatto fornendo un metodo toString . Ogni volta che un oggetto viene registrato o stampato, viene chiamato il metodo toString . Il modo più semplice qui è stampare il valore di ciascun campo. Ecco un esempio:
public class Contact {
    // ...
    @Override
    public String toString() {
        return "Contact[" +
                "name='" + name + '\'' +
                ", email=" + email +
                ']';
    }
}

2. Riduci i modelli con i record

Poiché la maggior parte degli oggetti di valore hanno le stesse esigenze e funzionalità, sarebbe bello semplificare il processo di creazione. Diamo un'occhiata a come le registrazioni aiutano a raggiungere questo obiettivo.

2.1. Conversione della classe Person in Record

Creiamo una voce della classe Contact che abbia la stessa funzionalità della classe Contact definita sopra.
public record Contact(String name, String email) {}
La parola chiave record viene utilizzata per creare una classe Record . I record possono essere elaborati dal chiamante allo stesso modo di una classe. Ad esempio, per creare una nuova istanza di voce, possiamo utilizzare la parola chiave new .
Contact contact = new Contact("John Doe", "johnrocks@gmail.com");

2.2. Comportamento predefinito

Abbiamo ridotto il codice a una riga. Elenchiamo cosa comprende:
  1. I campi nome ed e-mail sono privati ​​e definitivi per impostazione predefinita.

  2. Il codice definisce un "costruttore canonico" che accetta i campi come parametri.

  3. I campi sono accessibili tramite metodi simili a getter: name() e email() . Non esiste un setter per i campi, quindi i dati nell'oggetto diventano immutabili.

  4. Implementato il metodo toString per stampare i campi proprio come abbiamo fatto per la classe Contact .

  5. Metodi equals e .hashCode implementati . Includono tutti i campi, proprio come la classe Contact .

2.3 Costruttore canonico

Il costruttore predefinito accetta tutti i campi come parametri di input e li imposta su campi. Ad esempio, il costruttore canonico predefinito è mostrato di seguito:
public Contact(String name, String email) {
    this.name = name;
    this.email = email;
}
Se definiamo un costruttore con la stessa firma nella classe di registrazione, verrà utilizzato al posto del costruttore canonico.

3. Lavorare con i record

Possiamo modificare il comportamento della voce in diversi modi. Diamo un'occhiata ad alcuni casi d'uso e come ottenerli.

3.1. Sostituire le implementazioni predefinite

Qualsiasi implementazione predefinita può essere modificata sovrascrivendola. Ad esempio, se vogliamo modificare il comportamento del metodo toString , possiamo sovrascriverlo tra parentesi graffe {} .
public record Contact(String name, String email) {
    @Override
    public String toString() {
        return "Contact[" +
                "name is '" + name + '\'' +
                ", email is" + email +
                ']';
    }
}
Allo stesso modo, possiamo sovrascrivere i metodi equals e hashCode .

3.2. Kit di costruzione compatti

A volte vogliamo che i costruttori facciano qualcosa di più che inizializzare semplicemente i campi. Per fare ciò, possiamo aggiungere le operazioni necessarie alla nostra voce nel Costruttore compatto. Si chiama compatto perché non necessita di definire l'inizializzazione del campo o l'elenco dei parametri.
public record Contact(String name, String email) {
    public Contact {
        if(!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
    }
}
Tieni presente che non esiste un elenco di parametri e che l'inizializzazione del nome e dell'e-mail avviene in background prima dell'esecuzione del controllo.

3.3. Aggiunta di costruttori

È possibile aggiungere più costruttori a un record. Diamo un'occhiata ad un paio di esempi e limitazioni. Innanzitutto, aggiungiamo nuovi costruttori validi:
public record Contact(String name, String email) {
    public Contact(String email) {
        this("John Doe", email);
    }

    // replaces the default constructor
    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }
}
Nel primo caso, si accede al costruttore predefinito utilizzando la parola chiave this . Il secondo costruttore sovrascrive il costruttore predefinito perché ha lo stesso elenco di parametri. In questo caso, la voce stessa non creerà un costruttore predefinito. Esistono diverse restrizioni per i costruttori.

1. Il costruttore predefinito dovrebbe essere sempre chiamato da qualsiasi altro costruttore.

Ad esempio, il codice seguente non verrà compilato:
public record Contact(String name, String email) {
    public Contact(String name) {
        this.name = "John Doe";
        this.email = null;
    }
}
Questa regola garantisce che i campi siano sempre inizializzati. È inoltre garantito che le operazioni definite nel costruttore compatto vengano sempre eseguite.

2. Non è possibile sovrascrivere il costruttore predefinito se è definito un costruttore compatto.

Quando viene definito un costruttore compatto, viene creato automaticamente un costruttore predefinito con l'inizializzazione e la logica del costruttore compatto. In questo caso, il compilatore non ci consentirà di definire un costruttore con gli stessi argomenti del costruttore predefinito. Ad esempio, in questo codice la compilazione non avverrà:
public record Contact(String name, String email) {
    public Contact {
        if(!email.contains("@")) {
            throw new IllegalArgumentException("Invalid email");
        }
    }
    public Contact(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

3.4. Implementazione delle interfacce

Come con qualsiasi classe, possiamo implementare interfacce nei record.
public record Contact(String name, String email) implements Comparable<Contact> {
    @Override
    public int compareTo(Contact o) {
        return name.compareTo(o.name);
    }
}
Nota importante. Per garantire la completa immutabilità, i record non possono essere ereditati. Le voci sono definitive e non possono essere ampliate. Inoltre non possono estendere altre classi.

3.5. Aggiunta di metodi

Oltre ai costruttori, che sovrascrivono metodi e implementazioni dell'interfaccia, possiamo anche aggiungere qualsiasi metodo desideriamo. Per esempio:
public record Contact(String name, String email) {
    String printName() {
        return "My name is:" + this.name;
    }
}
Possiamo anche aggiungere metodi statici. Ad esempio, se vogliamo avere un metodo statico che restituisca un'espressione regolare rispetto alla quale possiamo controllare la posta elettronica, allora possiamo definirlo come mostrato di seguito:
public record Contact(String name, String email) {
    static Pattern emailRegex() {
        return Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
    }
}

3.6. Aggiunta di campi

Non possiamo aggiungere campi istanza a un record. Tuttavia, possiamo aggiungere campi statici.
public record Contact(String name, String email) {
    private static final Pattern EMAIL_REGEX_PATTERN = Pattern
            .compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);

    static Pattern emailRegex() {
        return EMAIL_REGEX_PATTERN;
    }
}
Tieni presente che non esistono restrizioni implicite nei campi statici. Se necessario, potrebbero essere disponibili al pubblico e non definitivi.

Conclusione

I record sono un ottimo modo per definire le classi di dati. Sono molto più convenienti e potenti dell'approccio JavaBeans/POJO. Poiché sono facili da implementare, dovrebbero essere preferiti rispetto ad altri modi di creare oggetti di valore.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION