Видове класове: record и enum
Класът е основният механизъм за дефиниране на потребителски типове данни в Java. Освен обикновените класове езикът предоставя специализирани конструкции, които също дефинират типове. Такива конструкции са record и enum.
Обикновеният клас се използва, когато трябва да се опишат данни и поведение с пълен контрол върху полетата, конструкторите и методите. Записът record се използва основно за компактно представяне на данни. Изброеният тип enum се използва за фиксиран набор от допустими стойности.
Записи (record)
Записът record е специален вид клас, предназначен за моделиране на обекти, които основно съхраняват данни. Той е подходящ, когато даден тип трябва да съдържа няколко стойности и не се очаква тези стойности да се променят след създаване на обекта.
Обикновеният клас изисква ръчно деклариране на полета, конструктор, методи за достъп и често методи като toString(), equals() и hashCode(). При record голяма част от този код се генерира автоматично от компилатора.
Декларация на record
record Student(String name, int facultyNumber) {
}
В този пример Student е запис с два компонента:
nameот типString;facultyNumberот типint.
Компонентите в заглавната част на record описват данните, които записът съхранява. За всеки компонент компилаторът автоматично създава частно финално поле и публичен метод за достъп.
Създаване на обект от record
Обект от record се създава чрез ключовата дума new, както при обикновен клас.
Student student = new Student("Ivan Petrov", 12345);
При създаването се извиква автоматично генерираният конструктор. Подадените аргументи трябва да съответстват по ред, тип и брой на компонентите в декларацията на record.
Достъп до стойностите
При record методите за достъп не използват стандартната форма getName(). Вместо това всеки метод има същото име като съответния компонент.
System.out.println(student.name());
System.out.println(student.facultyNumber());
Методът name() връща стойността на компонента name. Методът facultyNumber() връща стойността на компонента facultyNumber.
Автоматично генерирани елементи
За един record компилаторът автоматично създава:
- частни финални полета за компонентите;
- конструктор, който приема стойности за всички компоненти;
- методи за достъп до компонентите;
- метод
toString(); - метод
equals(Object other); - метод
hashCode().
Това означава, че два обекта от един и същ record могат да бъдат сравнявани по стойност чрез equals(). При обикновен клас такова поведение трябва да се дефинира ръчно, ако е необходимо.
record Point(int x, int y) {
}
public class Application {
public static void main(String[] args) {
Point first = new Point(10, 20);
Point second = new Point(10, 20);
System.out.println(first.equals(second));
System.out.println(first);
}
}
Резултатът от first.equals(second) е true, защото двата обекта са от един и същ record тип и съдържат еднакви стойности в компонентите си.
Неизменимост на record
Компонентите на record са неизменими след създаване на обекта. Това означава, че стойностите се задават чрез конструктора и след това не могат да бъдат променяни чрез присвояване.
record Product(String name, double price) {
}
Product product = new Product("Keyboard", 59.90);
// product.price = 49.90; // не е позволено
Тази особеност прави record подходящ за пренасяне на данни между различни части на програмата, когато не трябва да се допуска случайна промяна на състоянието.
Компактен конструктор в record
В record може да се дефинира компактен конструктор. Той се използва, когато при създаване на обекта трябва да се изпълни допълнителен код, без да се изписва пълният списък с параметри.
record Product(String name, double price) {
Product {
name = name.trim();
}
}
В компактния конструктор не се присвояват ръчно стойности към полетата. След изпълнение на допълнителния код компилаторът автоматично записва параметрите в съответните компоненти.
Кога се използва record
record е подходящ, когато:
- типът основно съхранява данни;
- всички основни стойности са известни при създаване на обекта;
- не е необходима промяна на стойностите след създаване;
- трябва да се намали шаблонният код за конструктор, достъп до стойности и сравнение.
record не е подходящ, когато обектът трябва да има променливо състояние, сложен жизнен цикъл или много операции, които променят вътрешните му данни.
Изброени типове (enum)
Изброеният тип enum е специален вид клас, който дефинира фиксиран набор от именувани константи. Той се използва, когато дадена стойност трябва да бъде избрана само измежду предварително известни допустими стойности.
Пример:
enum Status {
NEW,
IN_PROGRESS,
COMPLETED,
CANCELLED
}
В този пример Status е тип, а NEW, IN_PROGRESS, COMPLETED и CANCELLED са неговите допустими стойности. Не може да се създаде произволна стойност от тип Status, която не е описана в декларацията на enum.
Използване на enum
Status status = Status.NEW;
Стойностите на enum се достъпват чрез името на типа и името на константата. Това прави кода по-четим и намалява риска от грешки, които биха възникнали при използване на обикновени текстови стойности.
if (status == Status.COMPLETED) {
System.out.println("The task is completed.");
}
Стойностите на enum могат да се сравняват чрез оператора ==, защото всяка константа е единствена предварително създадена инстанция на съответния изброен тип.
enum вместо текстови стойности
Без enum често се използват низове.
String status = "COMPLETED";
Този подход допуска грешки, защото компилаторът не може да провери дали текстът е валиден статус.
String status = "COMPLETEDD"; // технически валиден String, но невалиден статус
При enum подобна грешка се открива още при компилация.
Status status = Status.COMPLETED;
Вградени методи на enum
| Метод | Предназначение |
|---|---|
| values() | Връща масив с всички константи. |
| valueOf(String) | Връща константа по име. |
| ordinal() | Връща поредния номер на константата. |
| name() | Връща името на константата. |
Пример:
for (Status status : Status.values()) {
System.out.println(status);
}
Методът values() връща всички константи в реда, в който са декларирани. Методът valueOf(String) връща константа по точно име. Ако такова име не съществува, се получава грешка по време на изпълнение.
Status status = Status.valueOf("NEW");
System.out.println(status.name());
System.out.println(status.ordinal());
Методът name() връща името на константата като текст. Методът ordinal() връща позицията на константата, започвайки от 0. Не се препоръчва програмната логика да зависи от ordinal(), защото промяна в реда на константите променя и числовите им позиции.
Полета, конструктори и методи в enum
Изброеният тип може да съдържа полета, конструктори и методи. Това е възможно, защото enum е специален вид клас.
enum UserRole {
ADMIN("Administrator"),
MODERATOR("Moderator"),
USER("Standard user");
private final String description;
UserRole(String description) {
this.description = description;
}
String getDescription() {
return description;
}
}
В този пример всяка константа има собствена стойност за description. Конструкторът на enum се извиква автоматично при създаване на константите. Той не се извиква с new от външен код.
System.out.println(UserRole.ADMIN.getDescription());
Особености на enum
Конструкторът на enum не може да бъде публичен, защото константите са предварително определени в самата декларация. Нови стойности не могат да се създават извън изброения тип.
Използването на enum:
- ограничава допустимите стойности;
- подобрява четимостта на програмния код;
- осигурява проверка на типовете още при компилация;
- намалява вероятността от грешки.
enum е подходящ за статус на поръчка, ден от седмицата, сезон, вид плащане, потребителска роля или друг ограничен набор от стойности.
Сравнение между class, record и enum
| Конструкция | Основно предназначение | Създаване на стойности |
|---|---|---|
class | Описание на обекти с данни и поведение. | Чрез new, според дефинираните конструктори. |
record | Компактно описание на неизменими обекти за данни. | Чрез new, чрез автоматично генериран конструктор. |
enum | Описание на фиксиран набор от именувани стойности. | Само чрез константите, декларирани в enum. |