JavaRush /Java блог /Random UA /Кава-брейк #230. Що таке Записи (Records) в Java і як вон...

Кава-брейк #230. Що таке Записи (Records) в Java і як вони працюють

Стаття з групи Random UA
Джерело: JavaTechOnline У цій статті докладно розглянуто концепцію роботи записів (Records) в Java з прикладами, включаючи їх синтаксис, способи створення та використання. Кава-брейк #230.  Що таке Записи (Records) в Java і як вони працюють?Записи (Records) в Java - одна з чудових функцій, вперше показана в Java 14 як функція попереднього перегляду, і що остаточно з'явилася в релізі Java 17. Багато розробників активно її використовують, що допомагає їм успішно скорочувати величезну кількість шаблонного коду. Більше того: завдяки записам не потрібно писати жодного рядка коду, щоб зробити клас незмінним.

Коли використовувати Record в Java?

Якщо ви хочете передавати незмінні дані між різними рівнями вашої програми, то використання Record може стати хорошим вибором. За замовчуванням записи (Records) в Java є незмінними, а це означає, що ми не можемо змінити їх властивості після створення. В результаті це допомагає нам уникнути помилок та підвищує надійність коду. Простіше кажучи, використовуючи Record Java, ми можемо автоматично генерувати класи.

Де можна використовувати Record в Java?

Як правило, ми можемо використовувати записи в будь-якій ситуації, коли потрібно оголосити прості контейнери даних з незмінними властивостями та автоматично згенерованими методами. Наприклад, наведено нижче кілька варіантів використання, в яких записи можуть бути корисними: Об'єкти передачі даних (Data transfer objects, DTO): ми можемо використовувати Record для оголошення простих об'єктів передачі даних, які містять дані. Це корисно при передачі даних між різними рівнями програми, наприклад, між рівнем служби та рівнем бази даних. Об'єкти конфігурації : Record можна використовувати для оголошення конфігураційних об'єктів, які містять набір властивостей конфігурації для програми або модуля. Ці об'єкти зазвичай мають незмінні властивості, що робить їх потокобезпечними та простими у використанні. Об'єкти-значення. Record можна використовувати для оголошення об'єктів-значень, які містять набір значень, що становлять певну концепцію або модель предметної області. API Responses (відповіді API) : під час створення REST API дані зазвичай повертаються у формі JSON або XML. У таких випадках може знадобитися визначити просту структуру даних, що представляє відповідь API. Записи ідеально підходять для цього, тому що вони дозволяють визначити легку та незмінну структуру даних, яку можна легко серіалізувати у JSON або XML. Тестові дані. При написанні модульних тестів часто необхідно створювати тестові дані, які мають певний сценарій. У таких випадках може знадобитися визначити просту структуру даних, що представляє тестові дані. Records можуть бути ідеальними для цього, тому що вони дозволяють нам визначити легку та незмінну структуру даних із мінімальним шаблонним кодом. Tuple-подібні об'єкти : записи можуть використовуватися для оголошення Tuple-подібних об'єктів, які містять фіксовану кількість зв'язаних значень. Це може бути корисним при поверненні кількох значень методу або при роботі з колекціями пов'язаних значень.

Що таке запис (Record) у Java?

Запис (Record) у Java - це клас, призначений для зберігання даних. Він схожий на традиційний клас Java, але легший і володіє деякими унікальними функціями. Записи незмінні за умовчанням, що означає, що їх стан не може бути змінено після створення. Це робить їх ідеальними для зберігання незмінних даних, таких як параметри конфігурації або значення, повернені із запиту до бази даних. Також це спосіб створення типу користувача даних, що складається з набору полів або змінних, а також методів доступу до цих полів і їх зміни. Record Java спрощує роботу з даними за рахунок зменшення обсягу шаблонного коду, який розробники використовують для написання знову і знову. Синтаксис створення запису в Java:
record Record_Name(Fields....)

Як створити та використовувати Record в Java з прикладами?

Давайте розберемося, як створити і використовувати запис Java програмними методами.

Створення Record

Програмне створення запису не дуже схоже на створення звичайного класу Java. Замість class ми використовуємо ключове слово record . Також потрібно оголосити поля з типами даних у дужках імені запису. Ось приклад коду, який демонструє, як створити запис у Java:
public record Book(String name, double price) { }
У цьому прикладі ми створабо запис під назвою Book із двома полями: name і price . Ключове слово public вказує, що до цього запису можна отримати доступ поза межами пакета, в якому вона визначена.

Використання Record

Щоб використовувати запис у Java, ми можемо створити його екземпляр, застосовуючи ключове слово new, як ми це робимо із звичайним класом. Ось приклад:
Book book = new Book( "Core Java" , 324.25);
Тут створюється новий екземпляр запису Book з полем name , встановленим на Core Java , та полем price , встановленим на 324,25 . Після створення запису до її полів можна отримати доступ, використовуючи ім'я поля. Методів отримання та встановлення немає. Натомість ім'я поля стає ім'ям методу.
String name = book.name();

double price = book.price();
Тут ми бачимо вилучення значення полів name і price із запису Book . Крім полів запису також мають деякі вбудовані методи, які можна використовувати для керування ними. Наприклад, toString() , equals() та hashCode() . Пам'ятайте, що записи в Java за замовчуванням є незмінними, що означає, що після створення їх стан не можна змінити. Давайте розглянемо ще один приклад того, як створювати та використовувати записи в Java. Візьмемо випадок, де нам потрібний запис для зберігання інформації про студента.
public record Student(String name, int age, String subject) {}
Тут створюється запис під назвою Student з трьома полями: name , age та subject . Створити екземпляр цього запису ми можемо в такий спосіб:
Student student = new Student("John Smith", 20, "Computer Science");
Потім ми можемо отримати значення полів, використовуючи ім'я поля:
String name = student.name();
int age = student.age();
String subject= student.subject();

Як виглядає запис після компіляції?

Так як Record - це просто особливий вид класу, компілятор також перетворює його на звичайний клас, але з деякими обмеженнями та відмінностями. Коли компілятор перетворює запис (файл Java) на байт-код після процесу компіляції, створений файл .class містить деякі додаткові оголошення класу Record . Наприклад, наведений нижче байт-код, створений для запису Student компілятором Java:
record Student(String name, int age) {   }

Запис до файлу .class (після компіляції)

Ми також можемо знайти наведений нижче перетворений клас, якщо скористаємося інструментом javap і застосуємо наведену нижче команду з командного рядка. Важливо, що командний рядок Java включає інструмент javap , який можна використовувати для перегляду відомостей про поля, конструктори та методи файлу класу. >javap Student Висновок: якщо ми уважно перевіримо байт-код, то у нас можуть виникнути деякі спостереження:
  • Компілятор замінив ключове слово Record на class .
  • Компілятор оголосив клас як final . Це свідчить про те, що цей клас може бути розширений. Це також означає, що він не може бути успадкований і незмінний за своєю природою.
  • Перетворений клас розширює java.lang.Record . Це вказує на те, що всі записи є підкласом класу Record , визначеного в пакеті java.lang .
  • Компілятор додає параметризований конструктор.
  • Компілятор автоматично згенерував методи toString() , hashCode() та equals() .
  • Компілятор додав методи доступу до полів. Зверніть увагу на угоду про імена методів - вони точно збігаються з іменами полів, перед іменами полів не повинно бути get або set .

Поля у Records

Записи Java визначають свій стан за допомогою набору полів, кожне з яких має своє ім'я і тип. Поля запису оголошуються у заголовку запису. Наприклад:
public record Person(String name, int age) {}
Цей запис має два поля: name типу String та age типу int . Поля запису неявно є final і не можуть бути перепризначені після створення запису. Ми можемо додати нове поле, але це не рекомендується. Нове поле, що додається до запису, має бути статичним. Наприклад:
public record Person(String name, int age) {

   static String sex;
}

Конструктори в Records

Як і традиційні конструктори класів, конструктори у записах використовуються для створення екземплярів записів. У Records є дві концепції конструкторів: канонічний конструктор та компактний конструктор. Компактний конструктор забезпечує більш короткий спосіб ініціалізації змінних стану запису, тоді як канонічний конструктор надає більш традиційний спосіб з більшою гнучкістю.

Канонічний конструктор

Компілятор Java за промовчанням надає нам конструктор з усіма аргументами (конструктор з усіма полями), який надає свої аргументи відповідним полям. Він відомий як канонічний архітектор. Ми також можемо додати бізнес-логіку, таку як умовні оператори, для перевірки даних. Нижче наведено приклад:
public record Person(String name, int age) {

       public Person(String name, int age) {
           if (age < 18) {
              throw new IllegalArgumentException("You are not allowed to participate in general elections");
           }
      }
}

Компактний конструктор

Компактні конструктори ігнорують усі аргументи, включаючи круглі дужки. Призначення відповідних полів відбувається автоматично. Наприклад, наступний код демонструє концепцію компактного конструктора в Records :
public record Person(String name, int age) {

      public Person {
          if (age < 18) {
              throw new IllegalArgumentException("You are not allowed to participate in general elections");
          }
     }
}
Крім компактного конструктора ви можете визначити звичайні конструктори в Record , як і в звичайному класі. Однак треба переконатися, що всі конструктори ініціалізують усі поля запису.

Методи у Records

Записи Java автоматично генерують набір методів для кожного поля в записі. Ці методи називаються методами доступу та мають те саме ім'я, що й поле, з яким вони пов'язані. Наприклад, якщо в записі є поле з ім'ям price , то вона автоматично матиме метод з ім'ям price() , який повертає значення поля price . Крім автоматично згенерованих методів доступу, ми також можемо визначити наші власні методи в записі, як і в звичайному класі. Наприклад:
public record Person(String name, int age) {
   public void sayHello() {
      System.out.println("Hello, my name is " + name);
   }
}
Цей запис має метод sayHello() , який виводить вітання, використовуючи поле name .

Як Record допомагає скоротити шаблонний код

Давайте розглянемо простий приклад, щоб побачити, як записи в Java можуть допомогти позбавитися шаблонного коду. Припустимо, у нас є клас з ім'ям Person , який представляє людину з ім'ям, віком та адресаою електронної пошти. Ось як ми визначаємо клас у традиційний спосіб. Код без використання Record :
public class Person {

    private String name;
    private int age;
    private String email;

    public Person(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    public String getName() {

       return name;
    }

    public int getAge() {
       return age;
    }

    publicString getEmail() {
       return email;
    }

    public void setName(String name) {
       this.name = name;
    }

    public voidsetAge(int age) {
       this.age = age;
    }

    public void setEmail(String email) {
       this.email = email;
    }

    @Override
    public String toString() {
       return "Person{" +
         "name='" + name + '\'' +
         ", age=" + age +
         ", email='" + email + '\'' +
      '}';
    }
}
Як бачимо, цього класу потрібно багато шаблонного коду визначення полів, конструктора, гетерів, сеттерів і методу toString() . Крім того, припустимо, що ми хочемо зробити цей клас незмінним. Для цього ми маємо зробити деякі додаткові кроки, такі як:
  • Оголосити клас як final , щоб його не можна було розширити.
  • Оголосити всі поля private та final , щоб їх не можна було змінити поза конструктором.
  • Не надавати жодних методів setter для полів.
  • Якщо будь-яке з полів змінюється, потрібно повернути їх копію замість повернення вихідного об'єкта.
Код з використанням Record :
public record Person(String name, int age, String email) {}
От і все! Усього одним рядком коду ми визначабо клас, який має самі поля, конструктор, гетери, сеттери і метод toString() , як і традиційний клас. Синтаксис Record подбає про весь шаблонний код за нас. З наведеного вище прикладу ясно, що використання записів Java може допомогти позбутися шаблонного коду, надаючи більш короткий синтаксис для визначення класів з фіксованим набором полів. У порівнянні з традиційним підходом для визначення та використання записів потрібно менше коду, і записи забезпечують прямий доступ до полів за допомогою методів оновлення полів. Це робить код більш зручним для читання, ремонтопридатним і менш схильним до помилок. Крім того, з синтаксисом Record нам не потрібно робити нічого додатково, щоб зробити клас незмінним. За замовчуванням всі поля запису є final , а сам клас запису незмінюємо. Отже, створення незмінного класу з використанням синтаксису запису набагато простіше і вимагає менше коду, ніж традиційний підхід. З записами нам не потрібно оголошувати поля як final , надавати конструктор, який ініціалізує поля, або надавати гетери для всіх полів.

Загальні класи записів

У Java також можна визначити загальні класи Records . Універсальний клас запису – це клас запису, який має один або декілька параметрів типу. Перед вами приклад:
public record Pair<T, U>(T first, U second) {}
У цьому прикладі Pair - це універсальний клас запису, який приймає два параметри типу T і U. Примірник цього запису ми можемо створити так:
Pair<String, Integer>pair = new Pair<>( "Hello" , 123);

Вкладений клас усередині Record

Всередині запису також можна визначити вкладені класи та інтерфейси. Це корисно для угруповання пов'язаних класів та інтерфейсів, також це може допомогти покращити організацію та зручність супроводу кодової бази. Ось приклад запису, що містить вкладений клас:
public record Person(String name, int age, Contact contact){

    public static class Contact {

       private final String email;
       private final String phone;

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

       public String getEmail(){
          return email;
       }

       public String getPhone(){
          return phone;
       }
    }
}
У цьому прикладі Person - це запис, що містить вкладений клас Contact . У свою чергу, Contact – це статичний вкладений клас, який містить два приватні кінцеві поля: адресау електронної пошти та телефон. Він також має конструктор, який приймає електронну пошту та номер телефону, і два методи отримання: getEmail() та getPhone() . Примірник Person ми можемо створити так:
Person person = new Person("John",30, new Person.Contact("john@example.com", "123-456-7890"));
У цьому прикладі ми створабо новий об'єкт Person з ім'ям John , віком 30 років та новий об'єкт Contact з електронною поштою john@example.com та телефоном 123-456-7890 .

Вкладений інтерфейс усередині Record

Ось приклад запису, що містить вкладений інтерфейс:
public record Book(String title, String author, Edition edition){
    public interface Edition{
       String getName();
   }
}
У цьому прикладі Book - це запис, що містить вкладений інтерфейс Edition . У свою чергу, Edition це інтерфейс, що визначає єдиний метод getName() . Екземпляр Book ми можемо створити так:
Book book = new Book("The Hitchhiker's Guide to the Galaxy", "Douglas Adams", new Book.Edition() {

   public String getName() {

      return "Science Fiction";
   }
});
У цьому прикладі ми створюємо новий об'єкт Book із заголовком The Hitchhiker's Guide to the Galaxy , автором Douglas Adams і новою анонімною реалізацією інтерфейсу Edition , яка повертає ім'я Science Fiction , коли викликається метод getName() .

Що ще можуть робити Records?

  • Записи можуть визначати користувальницькі конструктори. Записи підтримують параметризовані конструктори, які можуть викликати за замовчуванням конструктор з наданими параметрами у своїх тілах. Крім того, записи також підтримують компактні конструктори, які аналогічні конструкторам за замовчуванням, але можуть містити додаткові функції, такі як перевірки в тілі конструктора.
  • Як і будь-який інший клас у Java, Record може визначати та використовувати методи екземпляра. Це означає, що ми можемо створювати та викликати методи, специфічні для класу запису.
  • У Record визначення змінних примірників як членів класу не допускається, оскільки вони можуть бути вказані лише як параметри конструктора. Однак записи підтримують статичні поля та статичні методи, які можна використовувати для зберігання та доступу до даних, загальним для всіх екземплярів класу запису.

Чи може запис реалізовувати інтерфейси?

Так, запис Java може реалізовувати інтерфейси. Наприклад, наведений нижче код демонструє концепцію:
public interface Printable {
   void print();
}
public record Person(String name, int age) implements Printable {
   public void print() {
      System.out.println("Name: " + name + ", Age: " + age);
   }
}
Тут ми визначабо інтерфейс Printable із єдиним методом print() . Ми також визначабо запис Person , який реалізує інтерфейс Printable . Запис Person має два поля: name і age і перевизначає метод друку інтерфейсу Printable для друку значень цих полів. Ми можемо створити екземпляр запису Person і викликати його метод print так:
Person person = new Person("John", 30);
person.print();
Це виведе на консоль Name: John, Age: 30 . Як показано в прикладі, записи в Java можуть реалізовувати інтерфейси так само, як і звичайні класи. Це може бути корисним для додавання поведінки до запису або для забезпечення відповідності запису контракту, визначеному інтерфейсом.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ