Record klass

Record klassi eesmärk on lihtsustada andmete hoiustamist. Tüüpiliselt tehakse selle jaoks tavaline klass erinevate väljadega ning getter/setter meetoditega. Record klass teeb seda lihtsamalt - piisab sellest kui väljad ära deklaleerida konstruktoris.

POJO (Plain-Old-Java-Object) lähenemine

Võtame näiteks järgneva klassi - Person klass, mis hoiab inimese kohta algseid andmeid:

public class Person {
    private final int id
    private final String firstName;
    private final String lastName;

    public Person(int id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public int getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    // equals? hashCode? toString?

}

Nagu oleme harjunud, siis igale väljale peab koostama juurde getterid/setterid. Kuna klass muud eesmärki ei täida, siis võib antud koodi kirjutamine tüütuks ja korduvaks muutuda. Sellest tuleneb ka väljend "boilerplate" - korduv kood (väheste muudatustega mujalt koodibaasist), mida peab kirjutama, et oleks minimaalne funktsionaalsus olemas.

POJO kasutades Project lombok teeki

Eelnevat koodi on võimalik lihtsustada väliste teekidega, nagu näiteks Project lombok: https://projectlombok.org

import lombok.Getter;
import lombok.ToString;
import lombok.EqualsAndHashCode;
import lombok.AllArgsConstructor;

@Getter                 // Generates public getter methods for all fields
@AllArgsConstructor     // Generates a constructor which fills all fields
@ToString               // Generates a toString method that includes all fields
@EqualsAndHashCode      // Genereates a equals and hashCode method
public class Person {
    private int id;
    private String firstName;
    private String lastName;
}

Lomboki annotatsioonide @Getter, @AllArgsConstructor, @ToString ja @EqualsAndHashCode-i abil saame sama funktsionaalsusega koodi luua. Koodi kompileerimisel tõlgitakse kood ümber tavaolekusse ehk lõpptulemusse satub ikkagi nii-öelda "boilerplate" kood.

Record lähenemine

Alates Java 14-st on võimalik kasutada Record klasse, mille ainus eesmärk on andmeid hoida:

public record Person(int id, String firstName, String lastName) {}

Sellest ühest reast:

  • Koostatakse final väljad igale parameetrile konstruktoris

  • Konstruktor, millega klassist objekt luua

  • Getter meetodid (id(), fistName(), lastName())

  • Equals meetod, mis kontrollib kõikide väljade samaväärsust

  • HashCode meetod

  • toString meetod

Record klassidest objekte luuakse nagu tavapäraseid objekte:

Person person = new Person(1, "Stu", "Dent");

Andmeväljadelt infot saadakse järgnevalt:

System.out.println(person.id());        // 1
System.out.println(person.firstName()); // Stu
System.out.println(person.lastName());  // Dent

Ning equals(), hashCode() ja toString() kutsutakse välja nagu igal muul objektil.

Record klasse võiks kasutada siis, kui klassi ainus mõte on hoida muutumatuid andmeid. Muudel juhtudel võiks juba tavalise klassi teha ning sinna vajalik äriloogika jms. kirjutada.

Record-klass võimalused

Record klassi konstruktorit on võimalik üle kirjutada. Antud näites lisame id väljale piirangu (peab olema suurem kui 0):

public record Person(int id, String firstName, String lastName) {

    public Person {
        if (id <= 0) {
            throw new IllegalArgumentException("id must be greater than 0");
        }
    }
}

Samuti võib record klassis kasutada staatilisi välju. Muudame eelneva näite käsitsi id määramise automaatseks, koostades uue konstruktori.

NB: Uusi konstruktoreid tehes peab need tagasi delegeerima põhi konstruktorisse, kasutades this() võtmesõna.

public record Person(int id, String firstName, String lastName) {

    private static int nextId = 1;

    // Has to delegate to main constructor
    // Otherwise you will get a compilation error:
    // Non-canonical record constructor must delegate to another constructor
    public Person(String firstName, String lastName) {
        this(nextId++, firstName, lastName);    // This here is important, we delegate new constructor to main constructor
    }
}

Record klassi sisse võib ka kirjutada staatilisi ja tavalisi meetodeid. Viimase puhul võiks kaaluda juba tavalise klassi kasutamist (Kui seal on juba suurem loogika sees).

public record Person(int id, String firstName, String lastName) {
    private static int nextId = 1;
    public Person(String firstName, String lastName) {
        this(nextId++, firstName, lastName);
    }

    public static void incrementNextId() {
        nextId++;
    }

    public String getFullName() {
        return firstName + " " + lastName;
    }
}

Uuri lisaks: https://openjdk.org/jeps/395