Сравнение членов перечисления Java: == или equals ()? - PullRequest
1568 голосов
/ 17 ноября 2009

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

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

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

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

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

Ответы [ 14 ]

1415 голосов
/ 17 ноября 2009

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

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

1024 голосов
/ 30 мая 2010

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

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

JLS 8,9 Enums

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

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

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

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

  • Работает.
  • Это быстрее.
  • Во время выполнения безопаснее.
  • Безопаснее во время компиляции.
77 голосов
/ 17 ноября 2009

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

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

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

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

59 голосов
/ 14 февраля 2013

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

Тем не менее:

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

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

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

14 голосов
/ 21 апреля 2017

Я предпочитаю использовать == вместо 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, но вы импортируете второе перечисление (second.pckg.Category) вместо первого, не осознавая этого:

import second.pckg.Category;
...

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

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

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

operator == нельзя применить к "second.pckg.Category", "first.pckg.Category"

import second.pckg.Category; 
...

Category.JAZZ == item.getCategory() 
12 голосов
/ 15 октября 2011

Вот примерный временной тест для сравнения:

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

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

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

9 голосов
/ 17 ноября 2009

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

6 голосов
/ 26 октября 2015

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

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

4 голосов
/ 07 ноября 2015

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

3 голосов
/ 29 октября 2018

ТЛ; др

Другим вариантом является служебный метод 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-безопасность
    • Обе ноль ➙ true
    • Либо ноль ➙ false
    • Нет риска броска NullPointerException
  • Компактный, читаемый

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

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

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

return (a == b) || (a != null && a.equals(b));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...