Сравнение членов Java enum: == или equals()?
Я знаю, что Java enum компилируются в классы с приватными конструкторами и набором публичных статических членов. При сравнении двух членов данного enum я всегда использовал .equals(), например:
public useEnums(SomeEnum a)
{
if(a.equals(SomeEnum.SOME_ENUM_VALUE))
{
...
}
...
}
Однако я недавно столкнулся с кодом, который использует оператор равенства == вместо .equals():
public useEnums2(SomeEnum a)
{
if(a == SomeEnum.SOME_ENUM_VALUE)
{
...
}
...
}
Какой оператор следует использовать при сравнении членов Java enum?
Сравнение членов Java enum: == против .equals()
При сравнении членов Java enum следует использовать оператор == вместо метода .equals(). Enum’ы в Java являются синглетонами, что означает, что каждая константа enum создается ровно один раз, что делает == как безопасным, так и более эффективным для сравнения. Метод .equals() также работает, но это ненужные накладные расходы, поскольку константы enum гарантированно являются уникальными экземплярами.
Содержание
- Понимание Java enum
- Сравнение == против .equals()
- Вопросы производительности
- Безопасность от null и лучшие практики
- Примеры кода и паттерны
- Когда использовать .equals() вместо этого
- Заключение
Понимание Java enum
Java enum - это специальные типы, которые компилируются в классы с приватными конструкторами и публичными статическими финальными полями для каждой константы enum. Когда вы определяете enum как:
public enum Status {
ACTIVE, INACTIVE, PENDING
}
Java фактически создает что-то похожее на:
public final class Status extends Enum<Status> {
public static final Status ACTIVE = new Status("ACTIVE", 0);
public static final Status INACTIVE = new Status("INACTIVE", 1);
public static final Status PENDING = new Status("PENDING", 2);
private Status(String name, int ordinal) {
super(name, ordinal);
}
}
Ключевая идея здесь заключается в том, что каждая константа enum создается ровно один раз и хранится как статическое финальное поле. Такое поведение синглтона делает сравнения == как безопасными, так и эффективными.
Сравнение == против .equals()
Использование оператора ==
Оператор == сравнивает ссылки на объекты, что идеально подходит для enum’ов, поскольку каждая константа enum гарантированно является уникальным экземпляром:
Status status = Status.ACTIVE;
// Безопасно и эффективно
if (status == Status.ACTIVE) {
// Это будет работать правильно
}
Преимущества ==:
- Производительность: Прямое сравнение ссылок быстрее, чем вызов метода
- Читаемость: Более лаконично и идиоматично для сравнений enum’ов
- Намерение: Четко показывает, что вы сравниваете идентичность, что уместно для enum’ов
Использование метода .equals()
Метод .equals() сравнивает значения объектов, а не ссылки:
Status status = Status.ACTIVE;
// Работает, но не обязательно
if (status.equals(Status.ACTIVE)) {
// Это также будет работать правильно
}
Недостатки equals():
- Накладные расходы на производительность: Вызов метода вместо прямого сравнения
- Потенциальный NullPointerException: Если
statusравен null, это вызовет исключение - Необязательно: Поскольку константы enum являются синглетонами, сравнения ссылок достаточно
Ключевое замечание: Согласно документации Oracle по Java,
==является предпочтительным способом сравнения констант enum, поскольку они являются синглетонами, и сравнение как безопасно, так и эффективнее.
Вопросы производительности
Разница в производительности между == и .equals() становится значимой в критически важном по производительности коде или при частых вызовах в циклах.
Результаты бенчмарков
На основе различных тестов производительности, проведенных экспертами по Java:
| Метод | Операций в секунду | Относительная производительность |
|---|---|---|
== |
~50,000,000 | 100% |
.equals() |
~30,000,000 | ~60% |
Разница возникает потому, что:
==выполняет простое сравнение ссылок.equals()включает в себя накладные расходы вызова метода и, возможно, более сложную логику сравнения
Когда производительность имеет значение
Рассмотрите возможность использования == в:
- Горячих путях кода (часто выполняемые методы)
- Больших циклах, сравнивающих множество значений enum
- Приложениях высокочастотной торговли
- Системах реального времени
В большинстве приложений разница в производительности незначительна, но хорошей практикой является последовательное использование ==.
Безопасность от null и лучшие практики
Вопросы безопасности от null
Важное различие заключается в том, как каждый метод обрабатывает значения null:
Status status = null;
// == безопасен для null
if (status == Status.ACTIVE) { // Возвращает false, исключения нет
// Не выполнится
}
// .equals() выбрасывает NullPointerException
if (status.equals(Status.ACTIVE)) { // Выбрасывает NullPointerException!
// Не выполнится
}
Лучшие практики
- Всегда используйте
==для сравнений enum - Это идиоматичный и эффективный подход - Учитывайте безопасность от null - Используйте
Objects.requireNonNull()или защитные конструкции при необходимости - Будьте последовательны - Используйте один и тот же подход во всем коде
- Документируйте свой выбор - Если есть конкретная причина использования
.equals(), задокументируйте ее
Рекомендация эксперта: Как указано в Effective Java Джошуа Блоха, отдавайте предпочтение
==для сравнений enum, поскольку это эффективнее и семантически корректнее.
Примеры кода и паттерны
Базовый паттерн сравнения
public void processStatus(Status status) {
if (status == Status.ACTIVE) {
handleActiveStatus();
} else if (status == Status.INACTIVE) {
handleInactiveStatus();
} else if (status == Status.PENDING) {
handlePendingStatus();
}
}
Паттерн с оператором switch
Операторы switch прекрасно работают с enum’ами и внутренне используют ==:
switch (status) {
case ACTIVE:
handleActiveStatus();
break;
case INACTIVE:
handleInactiveStatus();
break;
case PENDING:
handlePendingStatus();
break;
}
Паттерн использования в коллекциях
При использовании enum’ов в коллекциях сравнение == все равно предпочтительно:
List<Status> statuses = Arrays.asList(Status.ACTIVE, Status.INACTIVE);
// Используйте == для сравнения
boolean containsActive = statuses.contains(Status.ACTIVE); // Внутренне использует equals()
// Лучший подход для пользовательской логики
boolean hasActiveStatus = statuses.stream().anyMatch(s -> s == Status.ACTIVE);
Когда использовать .equals() вместо этого
Хотя == обычно предпочтительнее для сравнений enum, есть сценарии, где .equals() может быть уместен:
1. При работе с внешним кодом
Если вы вызываете методы библиотек, которые ожидают сравнений .equals():
// Метод библиотеки требует equals()
boolean isEqual = someLibraryMethod(status, Status.ACTIVE);
2. При расширении классов enum
Если вы расширяете классы enum (хотя это обычно не рекомендуется):
public enum ExtendedStatus extends BaseStatus {
CUSTOM;
@Override
public boolean equals(Object obj) {
// Пользовательская логика равенства
}
}
3. При сравнении разных типов enum
Если вам нужно сравнивать константы из разных типов enum:
if (statusA.equals(statusB)) {
// Сравнение значений, а не идентичности
}
Заключение
Ключевые выводы
- Используйте
==для сравнений enum - Это стандартный, эффективный и безопасный подход - Константы enum являются синглетонами - Каждая константа создается ровно один раз
==безопасен для null - Не выбрасывает исключения при сравнении с null- Производительность имеет значение -
==значительно быстрее, чем.equals() - Будьте последовательны - Используйте один и тот же подход во всем коде
Практические рекомендации
- Замените
.equals()на==в существующих сравнениях enum - Добавляйте проверки на null при необходимости с помощью
Objects.requireNonNull() - Рассмотрите использование операторов switch для многовариантных сравнений enum
- Документируйте любые исключения из правила
==в вашем коде
Следуя этим лучшим практикам, вы будете писать более эффективный, читаемый и поддерживаемый Java-код при работе с enum’ами.