Порівняння членів Java enum: == або дорівнює ()?


1735

Я знаю, що перерахунки 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
Не ускладнювати. "==" є більш читабельним і працює, тому що перерахунки за замовчуванням є однотонними. Ref
lokesh

Відповіді:


1574

Обидва технічно коректні. Якщо ви подивитесь на вихідний код для .equals(), він просто відкладається ==.

Я використовую ==, однак, як це буде нуль безпечно.


64
Особисто мені не подобається «нульова безпека», яку згадують як причину використання ==.
Нивас

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

86
Майте на увазі, що читаючи ваш код, "==" може виявитися неправильним для читача, поки він / вона не вимкне і не подивиться на джерело для відповідного типу, а потім побачить, що це перерахунок. У цьому сенсі читати ".equals ()" менш відволікаюче.
Кевін Бурліон

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

63
Іноді код переробляється, і можливо, що перерахунок замінюється класом, і тоді == більше не гарантується, що він є правильним, тоді як рівне продовжує працювати безперебійно. З цієї причини рівний - явно кращий вибір. Якщо ви хочете отримати нульову безпеку (оскільки ви працюєте на базі коду, який не був написаний таким чином, щоб мінімізувати використання нулів), є Apache ObjectUtils або Guava's Object # рівний (або ви можете прокатати свою власну). Я навіть не згадаю слова "вистава", боюсь, що хтось по праву ляпає мене книгою Дональда Кнута.
laszlok

1113

Можна ==використовувати далі enum?

Так: переписки мають жорсткі елементи керування екземплярами, що дозволяє використовувати ==для порівняння екземплярів. Ось гарантія, надана мовною специфікацією (наголос від мене):

JLS 8,9 Енуми

Тип enum не має інших випадків, ніж ті, які визначені його константами enum.

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

Оскільки існує лише один примірник кожної enumконстанти, допустимо використовувати ==оператор замість equalsметоду при порівнянні двох посилань на об'єкти, якщо відомо, що принаймні одна з них відноситься до enumконстанти . ( equalsМетод в Enum- це finalметод, який просто викликає super.equalsсвій аргумент і повертає результат, виконуючи таким чином порівняння ідентичності.)

Ця гарантія є достатньо сильною, що рекомендує Джош Блок: якщо ви наполягаєте на використанні однотонного шаблону, найкращим способом його реалізації є використання одноелемента enum(див .: Ефективне Java 2-е видання, Пункт 3: Застосування властивості одиночного з допомогою приватний конструктор або тип 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;чи IMHO помилка та має бути виправлено, ваш перерахунок має два значення, а не три ( див. коментар Тома Хотіна) 4. Це безпечніше під час компіляції : Гаразд, але equalsвсе одно повернуться false. Зрештою, я не купую її, я віддаю перевагу equals()читабельності (див. Коментар Кевіна).
Паскаль Тивеннт

201
Чорні позначки проти Java вважають, що деякі люди думають, що foo.equals(bar)це читабельніше, ніжfoo == bar
Беннет МакЕлвей

19
Це працює: Поки вам не потрібно замінити enum класом, як частиною рефакторингу. Успіхів знайти весь код, використовуючи ==, який порушується. Це швидше: Навіть якщо це правда (сумнівно), є цитата Кнута про передчасну оптимізацію. Безпечніше під час виконання: "Безпечний" - це не перше слово, яке виникає на увазі, якщо використання == - це єдине, що відділяє вас від NullPointerException. Безпечніше під час компіляції: Не дуже. Це може вберегти вас від досить елементарної помилки порівняння неправильних типів, але знову ж таки, якщо саме так ваші нерозумні програмісти, "безпечно" - це одне, чого ви не є.
laszlok

35
"якщо це так нерозумно ваші програмісти," безпечно "- це одне, чого ви не є." - Я не згоден з цією цитатою. Ось що стосується перевірки типу: запобігання "тупому" помилкам під час компіляції, таким чином, навіть до запуску коду. Навіть розумні люди роблять німі помилки, краще деякі гарантії, ніж обіцянки.
ymajoros

7
@laszlok: звичайно, але якщо все може вийти з ладу, вони просто будуть. Мати паркан для якихось тупих помилок - це завжди добре. Існує достатньо місця для творчості для менш тривіальних помилок, нехай компілятор обробляє цей варіант, перш ніж ви намагатиметеся запустити свій код.
ymajoros

88

Використання ==для порівняння двох значень enum працює, оскільки для кожної постійної enum є лише один об'єкт.

З іншого боку, насправді не потрібно використовувати ==для написання нульового коду, якщо ви пишете equals()такий:

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

Це найкраща практика, відома як Порівнювати константи ліворуч, яких ви обов'язково повинні дотримуватися.


9
Я роблю це під час отримання .equals()рядкових значень, але я все ще вважаю, що це шалений спосіб написати нульовий код. Я набагато скоріше мати оператора (або метод, але я знаю, що [null object] .equals ніколи не буде безпечним для Java в Java через те, як працює точковий оператор), що завжди є безпечним для нуля, незалежно від порядку, в якому він використовується. Семантично оператор "рівний" повинен завжди рухатися.
Метт Бал

2
Ну, оскільки у Java немає оператора безпечної навігації Groovy ( ?.), я продовжуватиму використовувати цю практику при порівнянні з константами, і, TBH, я не вважаю це дурним, можливо, менш "природним".
Паскаль Thivent

32
nullПерерахування , як правило , помилка. У вас є перерахування всіх можливих значень, а ось ще одне!
Том Хоутін - тайклін

8
За винятком значень, які явно зазначаються як @Nullable, я вважаю Порівняння констант з лівого антипаттерном, оскільки воно поширює неоднозначність щодо того, які значення можуть чи не можуть бути нульовими. Типи Enum майже ніколи не повинні бути @Nullable, тому нульовим значенням буде помилка програмування; у такому випадку, чим раніше ви кинете NPE, тим більше шансів, що ви знайдете помилку програмування там, де ви допустили, що це недійсне. Тож "найкраща практика ... якої ви обов'язково слід дотримуватися" - це явно неправильно, коли мова йде про види перерахунків.
Тобіас

24
Порівняйте константи з лівих кращих практик, як найкращий англійський стиль у стилі Йода.
maaartinus

58

Як говорили інші, і те, ==і інше .equals()працюють у більшості випадків. Визначеність часу компіляції, що ви не порівнюєте абсолютно різних типів об'єктів, на які вказували інші, є достовірною і вигідною, однак особливий вид помилок порівняння об'єктів двох різних типів часу компіляції також знайде FindBugs (і, ймовірно, Eclipse / IntelliJ збирають перевірки часу), тому компілятор Java, виявивши це, не додає особливої ​​безпеки.

Однак:

  1. Той факт , що ==ніколи не кидає NPE на мій погляд , є недоліком в ==. Навряд чи має бути потреба у enumтипах null, оскільки будь-який додатковий стан, який ви можете висловити через, nullпросто може бути доданий до enumдодаткового екземпляра. Якщо це несподівано null, я вважаю за краще мати NPE, ніж ==мовчки оцінювати значення false. Тому я не погоджуюся з безпечнішою думкою під час виконання ; краще ввійти в звичку ніколи не допускати enumцінностей @Nullable.
  2. Аргумент , що ==це швидше , також підроблений. У більшості випадків ви звертаєтесь .equals()до змінної, типом якої часу компіляції є перелік enum, і в цих випадках компілятор може знати, що це те саме, що ==(оскільки метод 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ціль, щоб вони були трохи особливими. Strings теж трохи особливі.
CurtainDog

Синтаксис для операторів переключення не містить ані ==, ані .equals (), тому я не бачу, у чому ваша суть. Моя точка 3.) - про те, як легко перевірити код для читача. Семантика рівності висловлювань комутації є правильною, доки компіляція оператора 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. І це може бути трохи важко помітити.

Отже, якщо ви замість цього використовуєте оператор, ==ви отримаєте помилку компіляції:

operator == не можна застосувати до "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

Перший (дорівнює) виконує віртуальний виклик і випробовує зворотний буль з стека. Другий (==) порівнює адреси об'єктів безпосередньо зі стека. У першому випадку активності більше.

Я кілька разів проводив цей тест з обома ІФ, по одному. "==" стає настільки дещо швидшим.


Я підозрюю, що передбачення гілки компілятора може зробити цей тест недійсним. Розумний компілятор визнає, що обидва ці, якщо твердження, завжди будуть оцінюватись як хибні, і, таким чином, уникати декількох порівнянь. Дійсно розумний компілятор може навіть визнати, що значення в циклі y і x не вплинуть на результат і оптимізують всю справу ...
Даррел Хоффман,

Це могло б, але це точно не так. Я показую фактичний створений компілятором байт-код у своїй відповіді.
ChrisCantrell

ви обоє розгублені. :-) Прогнозування гілки працює під час виконання, тому різний байт-код не надто актуальний. Однак у цьому випадку прогнозування галузей допоможе обом випадкам однаково.
vidstige

7
Кого хвилює? Якщо ви не порівнюєте переліків тисячі разів у циклі під час відеоігри, зайва наносекунда не має сенсу.
user949300

Байт-код вкрай не має значення для продуктивності, якщо ви не форсуєте інтерпретований режим. Напишіть контрольний показник JMH, щоб побачити, що продуктивність точно така ж.
maaartinus


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`.

Переваги

Я знаходжу пару переваг цього методу:

  • Нульова безпека
    • Обидва нульові ➙ 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, у вас буде багато коду для зміни та тестування через попереднє використання ==.

Отже, найкраще дотримуватися того, що вважається доброю практикою, якщо немає виправданих вигод.


Гарна відповідь, але тут уже згадувалося .
шмосель

Гм. У цей момент питання стосуватиметься того, чи використовуєте ви == або дорівнює в інтерфейсі. Плюс всілякі запитання щодо того, чи справді розумно замінити реалізацію непорушного типу зміною. І, мабуть, розумно розібратися, яка хороша практика, перш ніж турбуватися про виправданий виграш. (імхо == - краща практика).
Робін Девіс

1

Одне з правил Сонару - це Enum values should be compared with "==". Причини такі:

Тестування рівності значення enum з equals()цілком справедливо, тому що enum є Об'єктом, і кожен відомий розробник 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, легкий для читання код і перевірку сумісності типу під час компіляції.

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


4
Чому це рішення краще ==? Коли ==ви отримуєте захист NPE, легкий для читання код та компіляцію перевірки сумісності типів часу, і ви отримуєте все це без написання спеціальних рівних методів.
Метт Бал

1
UNDEFINEDЗначення, ймовірно , хороша ідея. Звичайно, у Java 8 ви б використали Optionalнатомість.
Томаш

-7

==може кинути, NullPointerExceptionякщо примітивний тип порівнюється з його версією Class. Наприклад:

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
    }
}

2
Обсяг цього питання досить чіткий, і примітивні типи не входять у сферу застосування.
Том

@Тому дискусія, що ведеться тут, вводить в оману, яка ==ніколи не може кинути NPEs. На це запитання достатньо відповідей, але це може бути хорошим посиланням у тому випадку, якщо хтось, як я, потребував 2 годин, щоб потрапити на помилку / функцію прочитає це.
ayushgp

1
Я не можу кинути NPE, коли ви не ігноруєте тему цього питання. І навіть якщо ви збережете цю відповідь тут, чи вважаєте ви, що хтось із тим самим питанням, який ви мали, знайшов би цю відповідь, шукаючи щось інше? Цій людині потрібно було б шукати переліків, які вони, очевидно, не використовують. Ця відповідь мала б більше сенсу в цьому питанні: stackoverflow.com/questions/7520432/…
Том
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.