* 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) обекти.
Добави коментар