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

1565

Я знаю, что перечисления 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)
    {
        ...
    }
    ...
}

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

  • 7
    Я просто наткнулся на очень похожий вопрос: stackoverflow.com/questions/533922/…
  • 25
    Я удивлен тем, что во всех ответах (особенно в ответе на полигенные смазки, в котором подробно объясняется, почему == работает), не было упомянуто еще одно большое преимущество ==: в нем четко указано, как работают перечисления (как фиксированный набор синглтонов). объекты). С равными это заставляет задуматься, что может быть несколько экземпляров одной и той же альтернативы enum, плавающей вокруг.
Показать ещё 1 комментарий
Теги:
enums

17 ответов

1408
Лучший ответ

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

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

  • 55
    Лично мне не нравится «нулевая безопасность», упомянутая в качестве причины для использования ==.
  • 5
    Я второй ответ, что крошечное улучшение производительности во время выполнения тоже.
Показать ещё 22 комментария
1039

Может ли == использоваться для 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:

  • Оно работает.
  • Это быстрее.
  • Это безопаснее во время выполнения.
  • Это безопаснее во время компиляции.
  • 25
    Хороший ответ, но ... 1. Это работает : согласен 2. Это быстрее : докажите это для перечислений :) 3. Это безопаснее во время выполнения : Color nothing = null; это IMHO ошибка и должна быть исправлена, ваше перечисление имеет два значения, а не три (см. комментарий Тома Хоутина) 4. Это безопаснее во время компиляции : ОК, но equals все равно вернет false . В конце я не покупаю это, я предпочитаю equals() для удобочитаемости (см. Комментарий Кевина).
  • 5
    @Pascal: Я согласен, что null аргумент слабый, потому что null является спорным для начала (CAR Hoare "ошибка в миллиард долларов" и т. Д.). См. Также мой вопрос: stackoverflow.com/questions/2887761/… // Я действительно считаю, что проверка совместимости типов во время компиляции важна и намного лучше, чем «тихий сбой» во время выполнения путем возврата false . На самом деле меня не интересует преимущество в скорости, так как оно вряд ли будет значительным; Я просто повторяю то, что сказал Блох.
Показать ещё 16 комментариев
76

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

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

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

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

  • 8
    Я делаю это когда строковые значения .equals() , но я все еще думаю, что это дурацкий способ написать нулевой код. Я бы предпочел иметь оператор (или метод, но я знаю, что [нулевой объект] .equals никогда не будет нулевым в Java из-за того, как работает оператор точки), который всегда будет нулевым, независимо от порядок, в котором он используется. Семантически оператор «равно» должен всегда коммутировать.
  • 2
    Ну, поскольку в Java нет оператора безопасной навигации Groovy ( ?. ), Я буду продолжать использовать эту практику при сравнении с константами, и, TBH, я не нахожу это дурацким, может быть, просто менее "естественным".
Показать ещё 3 комментария
58

Как говорили другие, в большинстве случаев работают как ==, так и .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 типов.

  • 4
    Что касается пункта 3, я могу использовать enum в качестве цели switch поэтому они немного особенные. String тоже немного особенные.
  • 0
    Синтаксис для операторов switch не содержит ни ==, ни .equals (), поэтому я не понимаю, в чем ваша суть. Мой пункт 3.) о том, как легко проверить код для читателя. Семантика равенства операторов Switch является правильной, пока оператор switch компилируется.
12

Я предпочитаю использовать == вместо 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() 
11

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

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 по одному. "==" всегда немного быстрее.

  • 0
    Я подозреваю, что предсказание ветви компилятора может сделать этот тест недействительным. Умный компилятор распознает, что оба оператора if всегда будут иметь значение false, что позволит избежать многократного сравнения. Действительно умный компилятор может даже признать, что значения в циклах y и x не будут влиять на результат и оптимизировать все это ...
  • 0
    Может, но определенно нет. Я показываю фактический сгенерированный компилятором байт-код в моем ответе.
Показать ещё 3 комментария
10

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

  • 0
    это должно было быть в комментарии. не так ли?
4

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

4

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

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

3

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

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

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

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

  • 0
    Хороший ответ, но уже упоминалось здесь .
  • 0
    Um. В этот момент вопрос будет о том, используете ли вы == или равно в интерфейсе. Плюс все виды вопросов о том, действительно ли целесообразно заменить реализацию неизменяемого типа изменяемой реализацией. И, вероятно, разумно выяснить, что такое хорошая практика, прежде чем беспокоиться о оправданном выигрыше. (imho == это лучшая практика).
1

ТЛ; др

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

Objects.equals( thisEnum , thatEnum )

Objects.equals

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

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

Третьим вариантом является метод static equals найденный в классе утилиты Objects добавленном в Java 7 и более поздних версиях.

пример

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

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

Выгоды

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

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

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

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

Посмотрите сами, из исходного кода Java 10 OpenJDK:

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

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

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

1

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

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

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

  • 0
    Это неверно equals() не может быть переопределено в перечисляемых классах; это final метод. Это победило бы всю цель использования enum. stackoverflow.com/a/2964760/139010
  • 0
    @MattBall Спасибо. Я должен был попробовать это. Я перефразировал ответ.
0

== может 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
    }
}
  • 1
    Сфера довольно ясна в этом вопросе, а примитивные типы не входят в сферу.
  • 0
    @ То, что здесь идет обсуждение, вводит в заблуждение, что == никогда не может бросить NPEs . На этот вопрос достаточно ответов, но это может быть хорошим справочником в случае, если кто-то вроде меня, которому понадобилось 2 часа, чтобы добраться до ошибки / функции, прочитает это.
Показать ещё 1 комментарий
0

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

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

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

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

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

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

  • 3
    Почему это решение лучше, чем == ? С == вы получаете защиту NPE, легкий для чтения код и проверку совместимости типов времени компиляции, и вы получаете все это без написания каких-либо пользовательских методов, похожих на подобные.
  • 1
    UNDEFINED значение, вероятно, хорошая идея. Конечно, в Java 8 вы бы использовали Optional вместо этого.
-4

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

  • 2
    В Java перечисления - это не просто набор постоянных целых чисел. Они полные уроки.
  • 0
    Конечно, но использование «==» сводится к сравнению двух целых чисел. Enum в Java дает вам гораздо больше возможностей, вероятно, нет языка с такой обширной концепцией enuma.
Показать ещё 5 комментариев
-6

Я хотел бы явно выделить эту конкретную разницу между оператором == и 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).

  • 9
    Метод equals () всегда сравнивает содержимое двух объектов. Нет, это просто неправда. Object#equals() вызывает == .
  • 2
    Вопрос не в том, равны ли == и равны - пожалуйста, удалите
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню