[Перевод] Освоение Enum в Java: Руководство для разработчиков
Java предоставляет разработчикам специальный тип данных под названием Enum (перечисление), который позволяет создавать переменные, значения которых ограничены строго определенным списком. Этот список значений фиксирован и хорошо известен, что делает код более предсказуемым и понятным. Использование Enum помогает избежать ошибок, связанных с применением недопустимых значений, и способствует созданию более надежного и эффективного кода. Enum можно рассматривать как особый вид класса Java. Enum обладает рядом уникальных особенностей, таких как:
Возможность реализации интерфейсов;
Собственное пространство имен;
Неявная реализация интерфейсов Serializable и Comparable;
В неявном виде класс реализует интерфейс
java.lang.Enum
и не может быть расширен от другого класса;Для сравнения значений Enum можно использовать операторы
==
иequals()
.
Перечисления (enum) в Java представляют собой набор констант, которые обычно используются для определения некоторого ограниченного набора значений. В Java enum-классы имеют свои собственные конструкторы, которые объявляются с модификатором доступа private. Когда все конструкторы перечисления объявлены как private, это означает, что их можно вызывать только из самого перечисления, но не извне. Таким образом, применение оператора new для создания новых экземпляров перечислений невозможно.
Использование перечислений в Java является хорошей альтернативой использованию констант String (строковых) и Integer (целочисленных), поскольку они предоставляют более строгую типизацию и безопасность. При применении строковых констант легко допустить опечатки, а при использовании целочисленных констант их значения могут быть неочевидными и трудными для понимания.
Перечисления позволяют определить набор значений, которые может принимать переменная, и предоставляют удобный способ работы с этими значениями в программе. Таким образом, они являются эффективным и безопасным способом определения ограниченного набора возможных вариантов для определенного типа данных.
Enum также можно использовать в более сложных решениях. Например, вы можете определить несколько конструкторов для изменения значений каждого элемента перечисления. Кроме того, enum можно использовать для реализации паттернов проектирования singleton (одиночка) и strategy (стратегия). Паттерн singleton обеспечивает существование только одного экземпляра класса, а паттерн strategy позволяет динамически менять алгоритмы, используемые в программе, во время ее выполнения.
Для полноценного владения Enum в Java необходимо знать несколько важных методов:
name()
: конечный метод, который возвращает значение константы;toString()
: также возвращает значение константы, но может быть переопределен;ordinal()
: возвращает позицию константы, начиная с нуля;valueOf(String)
: создает перечисление из строкового значения;values()
: возвращает массив со значениями перечисления.
Теперь, когда вы знакомы с этими методами, можно перейти к практике и начать писать код.
Простое перечисление
Простое перечисление (Simple Enum) указывает на то, что это перечисление представляет собой основные именованные значения без каких-либо дополнительных свойств или методов. Оно может использоваться для простого перечисления констант или категорий, которые не требуют дополнительной сложности в своем определении. Это канонический пример перечисления с заранее определенными константами. Перечисление SimpleProtocol
содержит константы DHCP, HTTP, HTTPS, NFS
.
public enum SimpleProtocol {
DHCP, HTTP, HTTPS, NFS;
}
Самый простой способ использования перечисления — это:
SimpleProtocol dhcp = SimpleProtocol.DHCP;
System.out.println("Simple Protocol: " + dhcp);
Можно создать экземпляр перечисления с помощью методов valueOf()
и Enum.valueOf()
:
SimpleProtocol nfs = SimpleProtocol.valueOf("NFS");
System.out.println("From value: " + nfs);
System.out.println("From enum: " + Enum.valueOf(SimpleProtocol.class, "HTTP"));
Если вы используете valueOf()
, то необходимо проверить, существует ли необходимое значение в списке констант:
String value = "nfs";
try {
System.out.println(SimpleProtocol.valueOf(value));
} catch (Exception e) {
System.err.println("Invalid value: " + value);
}
Метод values()
перечисления позволяет нам получить массив всех его констант, а ordinal()
возвращает порядковый номер каждой константы. Перечисления можно перебирать как обычный массив, используя цикл for или while. Однако, в Java 8 появились новые инструменты для работы с коллекциями — потоки (streams) и лямбда-выражения. Они позволяют обрабатывать коллекции более эффективно и выразительно.
System.out.println("Values: " + Arrays.toString(SimpleProtocol.values()));
for (SimpleProtocol sp : SimpleProtocol.values()) {
System.out.println("Value: " + sp + ". Position: " + sp.ordinal());
}
Arrays.asList(SimpleProtocol.values()).stream().forEach(System.out::println);
Использование перечислений в операторе switch
:
SimpleProtocol protocol = SimpleProtocol.valueOf("NFS");
switch (protocol) {
case DHCP:
System.out.println("I am DHCP.");
break;
case HTTP:
System.out.println("I am HTTP.");
break;
case HTTPS:
System.out.println("I am HTTPS.");
break;
case NFS:
System.out.println("I am NFS.");
break;
}
Когда нужно сравнивать перечисления, которые представляют собой константы, разработчикам приходится использовать условные конструкции, такие как оператор if
. То есть необходимо проверить, равно ли значение перечисления какой-либо константе, и выполнить определенные действия в зависимости от результата сравнения. В качестве иллюстрации рассмотрим пример:
if (SimpleProtocol.DHCP == dhcp) {
System.out.println("We are equals.");
}
if (SimpleProtocol.DHCP.equals(dhcp)) {
System.out.println("Yes, equals.");
}
Перечисление с конструктором и полем
При создании перечислений в Java, конструкторы, используемые для инициализации полей, должны быть объявлены с модификатором доступа private. Это означает, что они недоступны за пределами самого перечисления. Тем не менее, поля перечисления могут иметь публичные методы доступа, которые позволяют получить возможность обращения к ним извне. Важно помнить, что поля и методы перечисления должны располагаться после списка констант, объявленных в начале перечисления.
public enum NamedProtocol {
DHCP("Dynamic Host Configuration Protocol"), HTTP("Hypertext Transfer Protocol"),
HTTPS("Hyper Text Transfer Protocol Secure"), NFS("Network File System");
private NamedProtocol(String fullName) {
this.fullName = fullName;
}
public String getFullName() {
return fullName;
}
private String fullName;
}
Вы можете использовать эту фичу следующим образом:
System.out.println("Full name: " + NamedProtocol.NFS.getFullName());
Перечисления и интерфейсы
Одной из особенностей перечислений в Java является их способность реализовывать интерфейсы. Рассмотрим интерфейс Descriptive
:
public interface Descriptive {
String getDescription();
String getFullName();
}
Теперь можно приступить к реализации упомянутого интерфейса в классе DescriptiveProtocol
. Для этого нам необходимо определить все методы, предусмотренные этим интерфейсом, внутри нашего класса.
package net.examples.enums;
public enum DescriptiveProtocol implements Descriptive {
DHCP("Dynamic Host Configuration Protocol", "Dynamically assigns an IP address."),
HTTP("Hypertext Transfer Protocol", "Foundation of data communication for the World Wide Web."),
HTTPS("Hyper Text Transfer Protocol Secure", "Extension of the HTTP used for secure communication."),
NFS("Network File System", "Distributed file system protocol.");
private DescriptiveProtocol(String fullName, String description) {
this.fullName = fullName;
this.description = description;
}
@Override
public String getDescription() {
return description;
}
@Override
public String getFullName() {
return fullName;
}
private String description;
private String fullName;
}
Для работы с этой фичей нет никаких сложностей:
DescriptiveProtocol https = DescriptiveProtocol.HTTPS;
System.out.println("Description: " + https.getDescription());
Множественные конструкторы
В коде присутствует константа HTTP
, которая инициализируется с помощью специального конструктора SecureProtocol(String, boolean)
, доступного только внутри класса. Этот конструктор принимает два аргумента: строку и логическое значение. Кроме того, для этой константы переопределен метод toString
, отвечающий за преобразование объекта в строковое представление. Также в коде представлен статический метод getSecureProtocols
, который использует современные возможности языка Java, такие как лямбда-выражения и потоки, для обработки данных.
public enum SecureProtocol {
DHCP("Dynamic Host Configuration Protocol"), HTTP("Hypertext Transfer Protocol", false),
HTTPS("Hyper Text Transfer Protocol Secure"), NFS("Network File System");
private SecureProtocol(String fullName) {
this.fullName = fullName;
}
private SecureProtocol(String fullName, boolean secure) {
this.fullName = fullName;
this.secure = secure;
}
public String getFullName() {
return fullName;
}
public boolean isSecure() {
return secure;
}
private String fullName;
private boolean secure = true;
@Override
public String toString() {
return "Name: " + getFullName() + ". Is secure: " + isSecure();
}
public static List getSecureProtocols() {
return Arrays.asList(values()).stream().filter(SecureProtocol::isSecure).collect(Collectors.toList());
}
}
Вот как мы можем их использовать. Например, для вывода строкового представления константы перечисления можно применить метод toString
. Вызвать его можно двумя способами: System.out.println(SecureProtocol.HTTP.toString())
или System.out.println(SecureProtocol.HTTPS)
. Список List
, возвращаемый методом getSecureProtocols
, может быть обработан с помощью простого лямбда-выражения forEach
или традиционного оператора for. Выберите подходящий вам способ перебора и обработки элементов списка, исходя из ваших предпочтений и требований к коду. Оба метода обеспечат вам доступ к элементам списка и позволят выполнить необходимые операции.
SecureProtocol.getSecureProtocols().stream().forEach(System.out::println);
Паттерн проектирования Singleton
Одним из эффективных способов реализации паттерна проектирования Singleton (одиночка) в Java является использование перечислений. Константы перечисления статичны. Благодаря этому мы можем быть уверены, что существует только один экземпляр перечисления. Этот способ реализации паттерна Singleton является наиболее простым и безопасным в многопоточной среде. Рассмотрим следующий пример:
public enum Counter {
INSTANCE;
private int value;
public void setValue(int value) {
this.value = value;
}
public void process() {
System.out.println(value);
}
}
Константа INSTANCE
является уникальным экземпляром класса, созданным на основе шаблона проектирования Singleton. Такой подход гарантирует, что в любой момент времени существует только один объект этого класса. Это означает, что мы можем использовать константу INSTANCE
для хранения одного конкретного значения, которое будет доступно во всех частях программы. При этом, если мы последовательно присвоим константе несколько значений, то в итоге будет сохранено и отображено только последнее из них. Ниже показан пример использования данной константы.
Counter firstInstance = Counter.INSTANCE;
firstInstance.setValue(1);
firstInstance.process();
Counter secondInstance = Counter.INSTANCE;
secondInstance.setValue(2);
secondInstance.process();
firstInstance.process();
Абстрактные методы
Абстрактные методы представляют собой особый тип методов в Java, которые объявляются с помощью ключевого слова abstract. Они не содержат никакой реализации, то есть не имеют тела метода, заключенного в фигурные скобки. Эти методы должны быть реализованы в классе-наследнике, который расширяет абстрактный класс, содержащий данный метод. Таким образом, абстрактные методы предоставляют только объявление метода, определяя его сигнатуру, но не предоставляют никакой конкретной реализации. Это позволяет создавать гибкие и расширяемые классы, которые могут быть адаптированы под конкретные требования.
В Java перечисление может включать в себя абстрактные методы. Каждая константа, объявленная в этом перечислении, обязана предоставить свою собственную реализацию данного метода. Таким образом, абстрактные методы в перечислении позволяют создавать общий интерфейс для всех констант, но при этом требуют индивидуального подхода к каждой из них.
public enum NamedProtocolWithAbstractMethod {
DHCP {
@Override
public String getFormattedName() {
return "I am the Dynamic Host Configuration Protocol";
}
},
HTTP {
@Override
public String getFormattedName() {
return "I am the Hypertext Transfer Protocol";
}
},
HTTPS {
@Override
public String getFormattedName() {
return "I am the Hyper Text Transfer Protocol Secure";
}
},
NFS {
@Override
public String getFormattedName() {
return "I am the Network File System";
}
};
public abstract String getFormattedName();
}
Процесс использования данного инструмента достаточно прост и интуитивно понятен:
NamedProtocolWithAbstractMethod nfsAbs = NamedProtocolWithAbstractMethod.NFS;
System.out.println(nfsAbs.getFormattedName());
Заключение
Перечисления (Enum) в Java предоставляют разработчикам удобный инструмент для создания констант. Они не ограничиваются только этим, так как могут включать в себя конструкторы, поля, а также реализовывать интерфейсы и абстрактные методы. Перечисление является подклассом java.lang.Enum
, благодаря чему имеет в своем арсенале полезные методы, такие как name()
для получения имени константы, toString()
для преобразования в строковое представление, ordinal()
для получения порядкового номера константы, valueOf(String)
для возврата константы по ее имени и values()
для получения массива всех констант перечисления.
https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html
В завершение напоминаем про открытый урок «Конструкторы и блоки инициализации», который пройдет 21 мая в рамках курса по углубленному изучению Java. На этом уроке мы:
Разберём конструктор на запчасти;
Определим финалистов (финальные переменные).
Наведём порядок (инициализации).
Записаться можно по ссылке.