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


1736

Я знаю, что перечисления 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/…
Мэтт Болл

39
Я удивлен тем, что во всех ответах (особенно на один из полигеномасляных материалов, который подробно объясняет, почему == работает), не было упомянуто еще одно большое преимущество ==: оно ясно показывает, как работают перечисления (как фиксированный набор синглтонов). объекты). С равными это заставляет задуматься о том, что может быть несколько экземпляров одной и той же «альтернативы» перечисления.
Стюарт Росситер

6
Будь проще. «==» более читабелен, и он работает, потому что по умолчанию перечисления одноэлементные. Ссылка
lokesh

Ответы:


1578

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

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


64
Лично мне не нравится «нулевая безопасность», упомянутая в качестве причины для использования ==.
Нивас

258
@Nivas: почему бы и нет? Вам нравится беспокоиться о порядке ваших аргументов (это относится только к сравнению констант слева, как в ответе Паскаля)? Вам нравится всегда проверять, что значение не является нулевым, прежде чем .equals()его использовать? Я знаю, что нет.
Мэтт Болл

86
Имейте в виду, что при чтении вашего кода, «==» может показаться некорректным для читателя, пока он / она не отключится и не посмотрит на источник соответствующего типа, а затем не увидит, что это перечисление. В этом смысле, это меньше отвлекает от чтения «.equals ()».
Кевин Бурриллион

60
@Kevin - если вы не используете IDE, которая позволяет вам видеть типы непосредственно при просмотре источника, вы обманываете себя - не проблема с правильной IDE.
MetroidFan2002

63
Иногда код подвергается рефакторингу, и вполне возможно, что enum заменяется классом, и в этом случае точка == больше не гарантируется как правильная, тогда как equals продолжает работать без проблем. По этой причине равенство, безусловно, лучший выбор. Если вы хотите нулевую безопасность (потому что вы работаете с базой кода, которая не была написана так, чтобы минимизировать использование нулей), есть Apache ObjectUtils или Guava's Objects # равно (или вы можете свернуть свое собственное). Я даже не буду упоминать слово «представление» из-за страха, что кто-то по праву дает мне пощечину с книгой Дональда Кнута.
Ласлок

1113

Может ==быть использован на enum?

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

JLS 8.9 Enums

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

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

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

Эта гарантия достаточно сильна, и Джош Блох рекомендует, что, если вы настаиваете на использовании одноэлементного шаблона, лучший способ реализовать его - это использовать одноэлементный enum(см. Effective Java 2nd Edition, Item 3: Enforment свойство singleton с помощью частный конструктор или тип enum, а также потокобезопасность в 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 предоставляют эту гарантию.

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

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

27
Хороший ответ, но ... 1. Это работает : согласен 2. Это быстрее : докажите это для перечислений :) 3. Это безопаснее во время выполнения : Color nothing = null;это ИМХО ошибка и должно быть исправлено, ваше перечисление имеет два значения, а не три ( см. комментарий Тома Хоутина) 4. Во время компиляции это безопаснее : хорошо, но equalsвсе равно вернется false. В конце я не покупаю это, я предпочитаю equals()для удобочитаемости (см. Комментарий Кевина).
Паскаль Тивент

201
Это черное пятно против Java, которое некоторые люди считают foo.equals(bar)более читабельным, чемfoo == bar
Беннет МакЭлви,

19
Это работает: пока вам не нужно заменить enum на класс как часть рефакторинга. Удачи в поиске всего кода, использующего ==, который ломается. Это быстрее: даже если это правда (сомнительно), есть цитата Кнута о преждевременной оптимизации. Безопаснее во время выполнения: «Безопасный» - это не первое слово, которое приходит на ум, если использование == - единственное, что отделяет вас от исключения NullPointerException. Безопаснее во время компиляции: не совсем. Это может защитить вас от довольно элементарной ошибки сравнения неправильных типов, но, опять же, если ваши программисты настолько глупы, «безопасность» - это единственное, чем вы не являетесь.
laszlok

35
«если вы настолько глупы, как ваши программисты,« безопасность »- это то, чем вы не являетесь». - Я не согласен с этой цитатой. Вот что такое проверка типов: предотвращение «глупых» ошибок во время компиляции, то есть даже до запуска кода. Даже умные люди делают глупые ошибки, лучше некоторые гарантии, чем обещания.
ymajoros

7
@laszlok: конечно, но если что-то не получится, они просто потерпят неудачу. Наличие ограждения для некоторых глупых ошибок - это всегда хорошо. Существует много возможностей для творческого подхода к менее тривиальным ошибкам, пусть компилятор справится с этим, прежде чем пытаться запустить свой код.
ymajoros

88

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

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

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

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


9
Я делаю это при .equals()вводе строковых значений, но я все еще думаю, что это дурацкий способ написания нулевого кода. Я бы предпочел иметь оператор (или метод, но я знаю, что [нулевой объект] .equals никогда не будет нулевым в Java из-за того, как работает оператор точки), который всегда будет нулевым, независимо от порядок, в котором он используется. Семантически оператор «равно» должен всегда коммутировать.
Мэтт Болл

2
Ну, поскольку у Java нет оператора безопасной навигации Groovy ( ?.), я буду продолжать использовать эту практику при сравнении с константами, и, TBH, я не нахожу это дурацким, может быть, просто менее "естественным".
Паскаль Thivent

32
nullПеречисление , как правило , ошибка. У вас есть это перечисление всех возможных значений, а вот еще одно!
Том Хотин - Tackline

8
За исключением значений, которые явно помечены как @Nullable, я считаю Сравнить константы слева антипаттерном, потому что он распространяет неоднозначность относительно того, какие значения могут быть или не быть нулевыми. Типы enum почти никогда не должны быть @Nullable, поэтому нулевое значение будет ошибкой программирования; в таком случае, чем раньше вы бросите NPE, тем больше вероятность того, что вы обнаружите ошибку в программировании, если допустите, что она будет нулевой. Таким образом, «лучшая практика ... которой вы обязательно должны следовать» совершенно неверна, когда дело доходит до типов enum.
Тобиас

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

58

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

Однако:

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

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

Таким образом, я все еще думаю, что аргументы в пользу использования .equals()для enumтипов.


4
Что касается пункта 3, я могу использовать его enumв качестве switchцели, поэтому они немного особенные. StringЭто тоже немного особенное.
CurtainDog

Синтаксис для операторов switch не содержит ни ==, ни .equals (), поэтому я не понимаю, в чем ваша суть. Мой пункт 3.) о том, как легко проверить код для читателя. Семантика равенства операторов switch является правильной, пока оператор switch компилируется.
Тобиас

17

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

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

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

package first.pckg

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

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

package second.pckg

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

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

import second.pckg.Category;
...

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

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

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

Оператор == не может быть применен к "second.pckg.Category", "first.pckg.Category"

import second.pckg.Category; 
...

Category.JAZZ == item.getCategory() 

Очень хорошие моменты, которые вы упомянули. Итак, я бы применил myenum.value (), а затем провел сравнение == для безопасности NPE.
Java Main

14

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

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());
    }   

}

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

 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

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

Я провел этот тест несколько раз с обоими IF, по одному. «==» немного быстрее.


Я подозреваю, что предсказание ветви компилятора может сделать этот тест недействительным. Умный компилятор распознает, что оба оператора if всегда будут иметь значение false и, таким образом, избегать многократного сравнения. Действительно умный компилятор может даже признать, что значения в циклах y и x не будут влиять на результат и оптимизировать все это ...
Даррел Хоффман

Может, но определенно нет. Я показываю фактический сгенерированный компилятором байт-код в моем ответе.
ChrisCantrell

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

7
Какая разница? Если вы не сравниваете перечисления тысячи раз в цикле во время видеоигры, дополнительная наносекунда не имеет смысла.
user949300

Байт-код довольно не важен для производительности, если вы не используете режим интерпретации. Напишите тест JMH, чтобы увидеть, что производительность точно такая же.
Маартин


7

ТЛ; др

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

Objects.equals( thisEnum , thatEnum )

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

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

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

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

пример

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

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

Льготы

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

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

Как это работает

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

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

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

6

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

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


4

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


1

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

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

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

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


Хороший ответ, но уже упоминалось здесь .
Шмосел

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

1

Одно из правил эхолота Enum values should be compared with "==". Причины следующие:

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

  • обеспечивает такое же ожидаемое сравнение (содержание), как equals()

  • более нуль-безопасен, чем equals()

  • обеспечивает проверку во время компиляции (статическую), а не проверку во время выполнения

По этим причинам использование ==должно быть предпочтительным equals().

Наконец, что не менее ==важно, перечисления могут быть более читабельными (менее подробными), чем equals().


0

Одним словом, у обоих есть плюсы и минусы.

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

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


-1

Хочу дополнить ответом полигеномасла:

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

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

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

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

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


4
Почему это решение лучше ==? С ним ==вы получаете защиту NPE, простой для чтения код и проверку совместимости типов времени компиляции, и вы получаете все это без написания каких-либо пользовательских методов, похожих на подобные.
Мэтт Болл

1
UNDEFINEDЗначение, вероятно , хорошая идея. Конечно, в Java 8 вы бы использовали Optionalвместо этого.
Томас
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.