Сравнение элементов перечисления Java: == или equals()?

Я знаю, что перечисления Java скомпилированы для классов с частными конструкторами и кучей публичных статических членов. При сравнении двух членов данного перечисления я всегда использовал .equals(), например.

public useEnums(SomeEnum a)
{
    if(a.equals(SomeEnum.SOME_ENUM_VALUE))
    {
        ...
    }
    ...
}

Однако я просто наткнулся на некоторый код, который использует оператор equals == вместо .equals():

public useEnums2(SomeEnum a)
{
    if(a == SomeEnum.SOME_ENUM_VALUE)
    {
        ...
    }
    ...
}

Какой оператор я должен использовать?

Ответы

Ответ 1

Оба являются технически правильными. Если вы посмотрите на исходный код для .equals(), он просто отменит ==.

Я использую ==, тем не менее, поскольку это будет безопасно.

Ответ 2

Может ли == использоваться для enum?

Да: перечисления имеют жесткий контроль над экземплярами, который позволяет использовать == для сравнения экземпляров. Здесь гарантия, предоставляемая спецификацией языка (выделение мной):

JLS 8.9 Enums

Тип перечисления не имеет экземпляров, отличных от тех, которые определены его константами перечисления.

Ошибка компиляции, чтобы попытаться явно создать экземпляр типа перечисления. final clone метод final clone в Enum гарантирует, что константные enum никогда не будут клонированы, а специальное обращение с помощью механизма сериализации гарантирует, что дублирующие экземпляры никогда не создаются в результате десериализации. Отражающее создание типов перечислений запрещено. Вместе эти четыре вещи гарантируют, что не существует экземпляров типа enum кроме тех, которые определены константами enum.

Поскольку существует только один экземпляр каждой константы enum, допустимо использовать оператор == вместо метода equals при сравнении двух ссылок на объекты, если известно, что хотя бы один из них ссылается на константу enum. (Метод equals в Enum является final методом, который просто вызывает super.equals по его аргументу и возвращает результат, таким образом выполняя сравнение идентичности.)

Эта гарантия достаточно сильна, что Джош Блох рекомендует, чтобы, если вы настаиваете на использовании шаблона singleton, наилучшим способом его реализации является использование enum с одним элементом (см. Эффективное Java 2nd Edition, пункт 3: Принудительное использование свойства singleton с частный конструктор или тип перечисления, а также безопасность потоков в Singleton)


Каковы различия между == и equals?

Напомним, что нужно сказать, что, как правило, == НЕ является жизнеспособной альтернативой equals. Однако, когда это возможно (например, с enum), есть два важных отличия:

== никогда не выбрасывает NullPointerException

enum Color { BLACK, WHITE };

Color nothing = null;
if (nothing == Color.BLACK);      // runs fine
if (nothing.equals(Color.BLACK)); // throws NullPointerException

== подлежит проверке совместимости типов во время компиляции

enum Color { BLACK, WHITE };
enum Chiral { LEFT, RIGHT };

if (Color.BLACK.equals(Chiral.LEFT)); // compiles fine
if (Color.BLACK == Chiral.LEFT);      // DOESN'T COMPILE!!! Incompatible types!

Должно ли == использоваться, когда это применимо?

Блох специально упоминает, что неизменяемые классы, которые имеют надлежащий контроль над своими экземплярами, могут гарантировать своим клиентам, что == можно использовать. enum конкретно указано для иллюстрации.

Пункт 1: Рассмотрите статические заводские методы вместо конструкторов

[...] он позволяет неизменному классу гарантировать, что не существует двух равных экземпляров: a.equals(b) тогда и только тогда, когда a==b. Если класс делает эту гарантию, то ее клиенты могут использовать оператор == вместо метода equals(Object), что может привести к повышению производительности. Типы перечислений предоставляют эту гарантию.

Итак, аргументы для использования == на enum:

  • Оно работает.
  • Это быстрее.
  • Это безопаснее во время выполнения.
  • Это безопаснее во время компиляции.

Ответ 3

Использование == для сравнения двух значений enum работает, потому что для каждой константы перечисления существует только один объект.

На стороне примечания нет необходимости использовать == для записи нулевого безопасного кода, если вы пишете свой equals() следующим образом:

public useEnums(SomeEnum a)
{
    if(SomeEnum.SOME_ENUM_VALUE.equals(a))
    {
        ...
    }
    ...
}

Это лучшая практика, известная как Сравнить константы слева, которые вы определенно должны соблюдать.

Ответ 4

Как говорили другие, в большинстве случаев работают как ==, так и .equals(). Точность времени компиляции, что вы не сравниваете совершенно разные типы объектов, которые указали другие, действительна и полезна, однако конкретный тип ошибок сравнения объектов двух разных типов времени компиляции также будет найден FindBugs (и, вероятно, Eclipse/IntelliJ), поэтому компилятор Java, обнаруживающий это, не добавляет дополнительной безопасности.

Однако:

  • Тот факт, что == никогда не бросает NPE в мой разум, является недостатком ==. Вряд ли когда-либо понадобится enum для типов null, так как любое дополнительное состояние, которое вы можете выразить через null, можно просто добавить в enum в качестве дополнительного экземпляра. Если это неожиданно null, я предпочел бы, чтобы NPE, чем ==, молча оценил значение false. Поэтому я не согласен с этим более безопасным во время исполнения; лучше войти в привычку никогда не позволять enum значениям @Nullable.
  • Аргумент, который == быстрее, также является фиктивным. В большинстве случаев вы вызываете .equals() для переменной, чей тип времени компиляции является классом enum, и в этих случаях компилятор может знать, что это то же самое, что и == (поскольку метод enum equals() может не переопределяться) и может оптимизировать вызов функции. Я не уверен, что компилятор в данный момент это делает, но если это не так, и в целом это проблема производительности в Java, я бы предпочел исправить компилятор, чем 100 000 программистов на Java изменили свой стиль программирования в соответствии с специфические характеристики производительности компилятора.
  • enums - Объекты. Для всех других типов объектов стандартное сравнение .equals(), а не ==. Я думаю, что опасно создавать исключение для enums, потому что вы можете случайно сравнить объекты с == вместо equals(), особенно если вы реорганизовываете enum в класс, отличный от enum. В случае такого рефакторинга, он работает сверху сверху неправильно. Чтобы убедиться в правильности использования ==, вам нужно проверить, является ли значение, которое является вопросом, как enum или примитив; если бы это был класс не enum, было бы неправильно, но легко пропустить, потому что код все равно будет компилироваться. Единственный случай, когда использование .equals() было бы неправильным, - если эти значения являются примитивами; в этом случае код не будет компилироваться, поэтому его гораздо сложнее пропустить. Следовательно, .equals() гораздо легче идентифицировать как правильное и безопаснее против будущих рефакторингов.

Я действительно думаю, что язык Java должен был определить == на объектах для вызова .equals() в левом значении и ввести отдельный оператор для идентификации объекта, но это не так, как была определена Java.

В целом, я все же считаю, что аргументы в пользу использования .equals() для enum типов.

Ответ 5

Я предпочитаю использовать == вместо equals:

Другая причина, помимо других, которые уже обсуждались здесь, заключается в том, что вы можете ввести ошибку, не осознавая ее. Предположим, что у вас есть это перечисления, которые являются точно такими же, но в разделенных pacakges (это не часто, но это может произойти):

Первое перечисление:

package first.pckg

public enum Category {
    JAZZ,
    ROCK,
    POP,
    POP_ROCK
}

Второе перечисление:

package second.pckg

public enum Category {
    JAZZ,
    ROCK,
    POP,
    POP_ROCK
}

Тогда предположим, что вы используете равные, как в item.category item.category, который является first.pckg.Category, но вы импортируете второе перечисление (second.pckg.Category) вместо первого, не понимая его:

import second.pckg.Category;
...

Category.JAZZ.equals(item.getCategory())

Итак, вы получите allways false, потому что это другое перечисление, хотя вы ожидаете true, потому что item.getCategory() - JAZZ. И это может быть немного сложно понять.

Итак, если вы используете оператор ==, у вас будет ошибка компиляции:

operator == не может применяться к "second.pckg.Category", "first.pckg.Category"

import second.pckg.Category; 
...

Category.JAZZ == item.getCategory() 

Ответ 6

Вот грубый тест времени для сравнения двух:

import java.util.Date;

public class EnumCompareSpeedTest {

    static enum TestEnum {ONE, TWO, THREE }

    public static void main(String [] args) {

        Date before = new Date();
        int c = 0;

        for(int y=0;y<5;++y) {
            for(int x=0;x<Integer.MAX_VALUE;++x) {
                if(TestEnum.ONE.equals(TestEnum.TWO)) {++c;}
                if(TestEnum.ONE == TestEnum.TWO){++c;}              
            }
        }

        System.out.println(new Date().getTime() - before.getTime());
    }   

}

Прокомментируйте IF по одному. Вот два сравнения сверху в дизассемблированном байт-коде:

 21  getstatic EnumCompareSpeedTest$TestEnum.ONE : EnumCompareSpeedTest.TestEnum [19]
 24  getstatic EnumCompareSpeedTest$TestEnum.TWO : EnumCompareSpeedTest.TestEnum [25]
 27  invokevirtual EnumCompareSpeedTest$TestEnum.equals(java.lang.Object) : boolean [28]
 30  ifeq 36

 36  getstatic EnumCompareSpeedTest$TestEnum.ONE : EnumCompareSpeedTest.TestEnum [19]
 39  getstatic EnumCompareSpeedTest$TestEnum.TWO : EnumCompareSpeedTest.TestEnum [25]
 42  if_acmpne 48

Первый (равно) выполняет виртуальный вызов и проверяет возвращаемое логическое значение из стека. Второй (==) сравнивает адреса объектов непосредственно из стека. В первом случае больше активности.

Я провел этот тест несколько раз с обоих IFs по одному. "==" всегда немного быстрее.

Ответ 7

В случае переименования оба правильные и правильные!!

Ответ 8

Использование чего-либо кроме == для сравнения констант перечисления - это нонсенс. Это как сравнивать объекты class с equals - не делайте этого!

Однако в Sun JDK 6u10 и более ранних версиях имелась неприятная ошибка (BugId 6277781), которая может быть интересной по историческим причинам. Эта ошибка препятствовала правильному использованию == в десериализованных перечислениях, хотя это, возможно, в некотором роде случайный случай.

Ответ 9

Перечисления - это классы, которые возвращают один экземпляр (например, одиночные числа) для каждой константы перечисления, объявленной public static final field (неизменяемой), так что оператор == может использоваться для проверки их равенства, а не с помощью метода equals()

Ответ 10

ТЛ; др

Другим вариантом является служебный метод Objects.equals.

Objects.equals( thisEnum , thatEnum )

Objects.equals для нулевой безопасности

оператор равенства == вместо .equals()

Какой оператор мне следует использовать?

Третий вариант - это метод статического equals найденный в служебном классе Objects добавленном в Java 7 и более поздние версии.

пример

Вот пример использования перечисления Month.

boolean areEqual = Objects.equals( Month.FEBRUARY , Month.JUNE ) ;  // Returns 'false'.

Выгоды

Я нахожу пару преимуществ для этого метода:

  • Null-безопасности
    • Оба null ➙ true
    • Либо ноль ➙ false
    • Нет риска выбросить NullPointerException
  • Компактный, читаемый

Как это устроено

Какая логика используется Objects.equals?

Убедитесь сами, из исходного кода Java 10 OpenJDK:

return (a == b) || (a != null && a.equals(b));

Ответ 11

Причина перечислений легко работает с ==, потому что каждый определенный экземпляр также является одиночным. Таким образом, сравнение идентичности с использованием == всегда будет работать.

Но использование ==, потому что оно работает с перечислением, означает, что весь ваш код тесно связан с использованием этого перечисления.

Например: Enums может реализовать интерфейс. Предположим, вы в настоящее время используете перечисление, которое реализует интерфейс1. Если позже, кто-то меняет его или вводит новый класс Impl1 в качестве реализации одного и того же интерфейса. Затем, если вы начнете использовать экземпляры Impl1, у вас будет много кода для изменения и тестирования из-за предыдущего использования ==.

Следовательно, лучше всего следовать тому, что считается хорошей практикой, если нет какой-либо оправданной выгоды.

Ответ 12

Короче говоря, оба имеют плюсы и минусы.

С одной стороны, имеет смысл использовать ==, как описано в других ответах.

С другой стороны, если вы по какой-либо причине замените перечисления на другой подход (обычные экземпляры класса), используя ==, вас укусит. (BTDT.)

Ответ 13

Вы можете использовать: ==, equals() или switch() block для сравнения Enums, все они являются технически истинными и будут служить вашим потребностям.

Проверьте этот учебник, чтобы узнать больше об общих операциях Enum: Как использовать Enums в java

Ответ 14

Я хочу дополнить ответ полигенными смазками:

Я лично предпочитаю equals(). Но это озерная проверка совместимости типов. Я думаю, что это важное ограничение.

Чтобы проверить совместимость типов во время компиляции, объявите и используйте пользовательскую функцию в своем перечислении.

public boolean isEquals(enumVariable) // compare constant from left
public static boolean areEqual(enumVariable, enumVariable2) // compare two variable

При этом у вас есть все преимущества обоих решений: защита NPE, легко читаемый код и проверка совместимости типов во время компиляции.

Я также рекомендую добавить значение UNDEFINED для перечисления.

Ответ 15

== может NullPointerException если примитивный тип сравнивается с его версией класса. Например:

private static Integer getInteger() {
    return null;
}

private static void foo() {
    int a = 10;

    // Following comparison throws a NPE, it calls equals() on the 
    // non-primitive integer which is itself null. 
    if(a == getInteger()) { 
        // Some code
    }
}

Ответ 16

Enum в середине представляет собой набор постоянных целых чисел. "==" так же корректно и правильно, как если бы вы сравнили два целых числа.

Ответ 17

Я хотел бы явно выделить эту конкретную разницу между оператором == и equals():

Метод equals() предназначен для проверки того, являются ли содержимое объекта (ов) ссылочной переменной (-ами) ссылкой (-ами) одинаковыми.

Оператор == проверяет, ссылается ли эта ссылочная переменная на ссылку (-ы) на тот же объект.

Это до класса реализации, чтобы обеспечить эту дифференциацию по мере необходимости приложением.

В противном случае поведение по умолчанию будет соответствовать классу Object (на Java), где, как описано в http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Object.html#equals(java.lang.Object):

Метод equals для класса Object реализует наиболее различающееся возможное отношение эквивалентности для объектов; то есть для любых непустых опорных значений x и y этот метод возвращает true тогда и только тогда, когда x и y относятся к одному и тому же объекту (x == y имеет значение true).