* Record в Java
Публикувано на 11 май 2023 в раздел ПИК3 Java.
В практиката често се работи с непроменими (immutable) обекти. Те се описват чрез класове, в които всички член променливи са private и final, и има само и единствено get методи. Така след като е създаден, обектът не може да се променя. Въпреки, че в зависимост от употребата може да се спестят някои елементи от шаблона, който ще покажа по-долу, едно цялостно решение би включило освен get методи до променливите, също метод hashCode(), метод equals() и метод toString():
package recordsexample; public class RecordsExample { public static void main(String[] args) { Person p1 = new Person("Ivan", "Ivanov", 20); System.out.println(p1.toString()); } } final class Person { private final String firstname; private final String lastname; private final int age; public Person(String firstname, String lastname, int age) { this.firstname = firstname; this.lastname = lastname; this.age = age; } @Override public int hashCode() { int result = 17; result = result * 37 + (this.firstname == null ? 0 : this.firstname.hashCode()); result = result * 37 + (this.lastname == null ? 0 : this.lastname.hashCode()); result = result * 37 + this.age; return result; } @Override public boolean equals(Object o) { if (!(o instanceof Person)) { return false; } Person p = (Person) o; if (p.firstname == null || p.lastname == null) { return false; } return (this.firstname.equals(p.getFirstname()) && this.lastname.equals(p.getLastName()) && this.age == p.getAge()); } @Override public String toString() { return "[firstname=" + this.firstname + ", lastname=" + this.lastname + ", age=" + this.age + "]"; } public String getFirstname(){ return this.firstname; } public String getLastName(){ return this.lastname; } public int getAge(){ return this.age; } }
С JDK 16 на помощ в такива ситуации идва новият тип непроменими (immutable) класове Record. Целият описан по-горе код може да бъде заменен с (почти) еквивалентния следен:
package recordsexample; public class RecordsExample { public static void main(String[] args) { Person p1 = new Person("Ivan", "Ivanov", 20); System.out.println(p1.toString()); } } record Person(String firstname, String lastname, int age){};
Споменах „почти“, защото имплементираният от мен метод hashCode() е различен от този, който се използва в record класа. Друга съществена разлика е, че get методите в record не съдържат думата "get" в името си. Например ако искате да прочетете първото име на човека, трябва да извикате метод "firstname":
Person p1 = new Person("Ivan", "Ivanov", 20); System.out.println(p1.firstname());
Това е направено нарочно, като най-вероятната причина е да се направи съществена отличителна разлика между record и JavaBean. Още една разлика е, че record е винаги final клас (не може да бъде наследяван), както и че той не може да наследява друг (позволено е обаче да имплентира интерфейси). От последното следва, че record не може да е и абстрактен.
Удобно нововъведение е, че record дават възможност за добавяне на специален „компактен конструктор“, с който се добавя валидация на данните. Например ако искаме да подсигурим, че годините не са отрицателно число, можем да направим следното:
record Person(String firstname, String lastname, int age){ public Person{ if(age<0) throw new IllegalArgumentException("Age cannot be negative"); if(firstname==null || lastname==null) throw new IllegalArgumentException("Names cannot be null"); } };
От тук насетне може да добавяте и нови методи или да предефинирате някои от съществуващите. Например можем да направим допълнителен конструктор, който приема годините по подразбиране със стойност 18, както и да добавим метод за връщане на пълното име на човека по следния начин:
record Person(String firstname, String lastname, int age){ public Person{ if(age<0) throw new IllegalArgumentException("Age cannot be negative"); if(firstname==null || lastname==null) throw new IllegalArgumentException("Names cannot be null"); } public Person(String firstname, String lastname){ this(firstname, lastname, 18); } String fullName(){ return this.firstname+" "+this.lastname; } };
Може да се досетите, че е възможно да създадете свой get метод, който просто препраща към стандартно генерирания. Това по-скоро би било лоша практика и е по-добре да се избягва.
Накрая ще изредя и някои ограничения за клас record:
- не може да наследява;
- не може да бъде наследяван;
- не може да се добавят допълнителни член променливи извън дефинираните в кръглите скоби (но е възможно да се дефинират статични променливи).
На практика record е създадено за удобство да не пишете голямо количество тривиален код. Възползвайте се когато е удачно. Единствено помнете, че той е само и единствено за непроменими (immuatable) обекти.
Добави коментар