Java Records este o caracteristică introdusă ca o caracteristică de previzualizare în Java 14 și finalizată ulterior, pentru a oferi o modalitate concisă și convenabilă de a declara clasele care sunt utilizate în principal pentru a păstra date. Acestea sunt concepute pentru a simplifica crearea de clase imuabile prin generarea automată a codului necesar pentru încapsularea câmpurilor de date, a constructorilor, a accesoriilor și a altor metode comune.

Principalele motivații din spatele introducerii înregistrărilor Java sunt:

Concizie: înregistrările reduc codul general prin generarea automată de implementări standard pentru sarcini obișnuite, cum ar fi accesorii de câmp, metodele equals(), hashCode() și toString(). Acest lucru permite dezvoltatorilor să se concentreze pe definirea câmpurilor de date și a comportamentului acestora.

Imuabilitate: înregistrările sunt concepute pentru a fi imuabile în mod implicit, ceea ce înseamnă că, odată ce un obiect este creat, starea acestuia nu poate fi modificată. Această proprietate este de dorit în multe scenarii în care integritatea și coerența datelor sunt importante.

Lizibilitate: prin utilizarea înregistrărilor, intenția codului devine mai clară. Înregistrările comunică în mod explicit că clasa este în primul rând un container de date cu un anumit set de câmpuri.

Potrivirea modelelor: Înregistrările Java funcționează bine cu caracteristica de potrivire a modelelor introdusă în Java 14. Ele pot fi folosite ca modele pentru a simplifica codul care implică extragerea și compararea datelor.

Cum se creează înregistrări?

Să creăm o clasă imuabilă:

public final class Account {

    private final String name;
    private final int id;
    private final String type;

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

    public String name() {
        return name;
    }

    public int id() {
        return id;
    }

    public String type() {
        return type;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Account that = (Account) o;
        return id == that.id && Objects.equals(name, that.name) && Objects.equals(type, that.type);
    }

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

   @Override
    public String toString() {
        return "Account{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", type='" + type + '\'' +
                '}';
    }
}

Acum să creăm aceeași clasă imuabilă folosind o înregistrare. Trebuie să folosim cuvântul cheie record pentru a crea o clasă de înregistrare în Java. La fel cum facem în constructori, trebuie să menționăm atributele și tipurile lor în înregistrare. În exemplul dat, Contul este un tip de înregistrare și este folosit pentru a păstra informații despre cont:

public record Account(String name, int id, String type) {}

După cum putem vedea, putem reduce o clasă completă cu o singură linie de cod. Am înlocuit cuvântul cheie class pentru a folosi în schimb cuvântul cheie record și am lăsat magia simplității să se întâmple.

Cuvântul cheie record din Java generează automat mai multe metode pentru noi, inclusiv un constructor, getters, equals(), hashCode() și toString(). Aceste metode sunt folosite pentru a crea și manipula obiecte din clasa de înregistrare în mod convenabil. De exemplu, după definirea înregistrării Account, putem crea o instanță a clasei Account astfel:

Account account = new Account("John Doe", 123456, "Savings");

Aceasta creează un nou obiect Account cu valorile specificate pentru câmpurile name, id și type. Deoarece este o înregistrare, putem accesa direct câmpurile folosind metodele getter generate. De exemplu:

String accountName = account.name();
int accountId = account.id();
String accountType = account.type();

System.out.println(accountName);
System.out.println(accountId);
System.out.println(accountType); 

// Output

John Doe
123456
Savings

Puncte importante:

  1. Nu putem extinde clasa Record, în mod explicit

Deși toate înregistrările extind clasa java.lang.Record, totuși nu putem crea o subclasă de java.lang.Record în mod explicit. Compilatorul nu va trece. Aceasta înseamnă că singura modalitate de a obține o înregistrare este declararea explicită a uneia

final class Account extends Record {   
// Compiler error : The type Data may not subclass Record explicitly
	private final int id;
}

2. Implementarea interfețelor

Clasele de înregistrare ne permit să implementăm interfețe. Putem implementa orice interfață ne dorim, fie că este o singură interfață sau mai multe interfețe.

public record Account( int id, String name) implements Runnable, Serializable

3. Nu ne putem defini propriile variabile de instanță

Când definim antetul, acesta reprezintă starea clasei de înregistrare. Aceasta înseamnă că nu putem avea nicio altă variabilă de instanță în interiorul înregistrării. Singura variabilă de instanță care ar fi creată este cea furnizată în componenta antet. Cu toate acestea, putem avea variabile statice în interiorul înregistrărilor care pot fi accesate în același mod ca și clasele folosind numele clasei de înregistrare.

4. Constructori multipli

Este posibil ca o definiție a tipului de înregistrare Java să conțină mai mulți constructori. Iată un exemplu de înregistrare Java care definește un constructor suplimentar pentru tipul de înregistrare de cont:

public record Account( int id, String name) {
     public Account(int id) {
        this(id, null);
    }
}

Constructorul suplimentar este declarat în interiorul corpului declarației de înregistrare a contului. Observați cum constructorul suplimentar apelează constructorul implicit al Înregistrării contului. Acest lucru este cerut de compilatorul Java, astfel încât compilatorul știe ce parametri constructor din constructorul suplimentar se potrivesc cu ce parametri din constructorul implicit.

Putem adăuga atât de mulți constructori suplimentari cât are sens pentru definiția noastră concretă Java Record.

5. Metode de instanță

Putem adăuga metode de instanță la o definiție Java Record - la fel cum putem face cu o clasă Java obișnuită. Iată un exemplu de definiție a înregistrării Java a contului din secțiunile anterioare, cu o metodă de instanță numită nameAsUpperCase() adăugată:

public record Account( int id, String name) {

    public static String nameAsUpperCase() {
        return name().toUpperCase();
    }
}

Observați cum metoda nameAsUperCase() apelează intern metoda name() generată automat.

6. Metode statice

De asemenea, este posibil să adăugați metode statice la o definiție Java Record. Iată un exemplu:

public record Account( int id, String name) {

public static String nameAsUpperCase(Account acc) {
        return acc.name.toUpperCase();
    }
}

7. Utilizarea adnotărilor

Putem adăuga adnotări la componentele unei înregistrări. De exemplu, putem aplica adnotarea @Transient câmpului id.

public record Account(@Transient Long id,String name, String type) 
            { // ... }

8. Serializare și deserializare

Serializarea a înregistrărilor în Java diferă de clasele obișnuite. Când un obiect de înregistrare este serializat, forma sa serializată constă dintr-o secvență de valori obținute din câmpurile de instanță finale ale obiectului. Formatul de flux al unui obiect de înregistrare rămâne același cu cel al unui obiect obișnuit din flux.

În timpul deserializării, dacă clasa locală corespunzătoare descriptorului de clasă de flux furnizat este o clasă de înregistrare, câmpurile de flux sunt citite și reconstruite pentru a reprezenta valorile componente ale înregistrării. Ulterior, un obiect de înregistrare este creat prin invocarea constructorului canonic al înregistrării cu valorile componente ca argumente. În cazul în care o valoare de componentă lipsește din flux, se folosește valoarea implicită pentru tipul de componentă.

În mod implicit, serialVersionUID al unei clase de înregistrare este 0L, dacă nu este declarat explicit. Cerința pentru potrivirea valorilor serialVersionUID este, de asemenea, eliminată pentru clasele de înregistrări. Procesul de serializare a obiectelor de înregistrare nu poate fi personalizat. Orice metode specifice clasei, cum ar fi writeObject, readObject, readObjectNoData, readResolve, writeExternal și readExternal definite în clasele de înregistrare sunt ignorate în timpul serializării și deserializării. Cu toate acestea, metoda writeReplace poate fi utilizată pentru a returna un obiect alternativ pentru serializare.

Înainte de a efectua orice serializare sau deserializare, trebuie să ne asigurăm că înregistrarea trebuie să fie serializabilă sau externalizabilă.

public record Account (...) implements Serializable { }

Concluzie

Tipurile de înregistrare în Java sunt o caracteristică foarte avantajoasă și servesc ca un plus valoros pentru orice sistem bazat pe Java. Prin adoptarea înregistrărilor, numeroase aplicații pot spori claritatea și concizia claselor lor de domenii. Mai mult, echipele pot elimina cerința pentru implementări realizate manual ale modelului de bază și pot reduce sau elimina dependența lor de biblioteci precum Lombok.

Învățare fericită!!