Яка перевага Java enum перед класом із загальнодоступними статичними кінцевими полями?


147

Я дуже добре знайомий з C #, але починаю більше працювати на Java. Я очікував, що я дізнаюся, що перерахунки на Java в основному еквівалентні тим, що в C #, але, мабуть, це не так. Спочатку я був із задоволенням дізнався, що переліки Java можуть містити кілька даних, що видається дуже вигідним ( http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html ). Однак з тих пір я виявив безліч особливостей, які є тривіальними в C #, такі як здатність легко присвоїти елементу enum певне значення, а отже, можливість перетворити ціле число на enum без гідної кількості зусиль ( тобто перетворити ціле значення у відповідність Java Enum ).

Отже, моє запитання таке: чи є якась користь для переписувачів Java над класом з купою публічних статичних фінальних полів? Або він просто надає більш компактний синтаксис?

EDIT: Дозвольте мені бути більш зрозумілим. Яка перевага перерахунків Java над класом із купою публічних статичних кінцевих полів одного типу ? Наприклад, у прикладі планет на першому посиланні, яка перевага перерахунку над класом із цими публічними константами:

public static final Planet MERCURY = new Planet(3.303e+23, 2.4397e6);
public static final Planet VENUS = new Planet(4.869e+24, 6.0518e6);
public static final Planet EARTH = new Planet(5.976e+24, 6.37814e6);
public static final Planet MARS = new Planet(6.421e+23, 3.3972e6);
public static final Planet JUPITER = new Planet(1.9e+27, 7.1492e7);
public static final Planet SATURN = new Planet(5.688e+26, 6.0268e7);
public static final Planet URANUS = new Planet(8.686e+25, 2.5559e7);
public static final Planet NEPTUNE = new Planet(1.024e+26, 2.4746e7);

Наскільки я можу сказати, відповідь casablanca - це єдине, що задовольняє це.


4
@Bohemian: Це може бути не дублікатом, оскільки в ОП є лише згадані public static finalполя, в яких можуть бути набрані значення, а не обов'язково ints.
casablanca

1
@Shahzeb Навряд чи. Очевидно, що використання переписів замість рядкових констант НАДАЧНО хороша ідея і більш ніж заохочується. Безпека типу, не потребує статичних функцій утиліти тощо. Абсолютно немає причин використовувати натомість рядки.
Voo

1
@Voo Так, я знав, що будуть розбіжності. І ось одна за 49 секунд. Енуми великі (і я їх люблю і дуже часто їх використовую), але який тип безпеки вам потрібен, коли оголошуєте постійну або використовуєте її. Це зайвий набір створювати enum кожного разу, коли потрібно оголошувати константу для Stral literal.
Шахзеб

4
@Shahzeb Якщо у вас є одна змінна, обов'язково використовуйте рядок, тут не може трапитися багато (одне значення є досить безглуздим як параметр). Але зауважте, що ми говоримо про постійні S, тому зараз ми говоримо про певне перенесення їх у функції тощо. Чи потрібен нам тип-сафтей? Ну, ні, але тоді більшість людей вважають типи с-стилю "все void*" хорошим стилем, і це може зупинити помилки (особливо, якщо передається більше одного параметра enum / string!). Крім того, він розміщує константи у власному просторі імен тощо. На відміну від цього, немає реальної переваги просто мати прості змінні.
Voo

3
@Bohemian: Я не бачу як. З ints не існує безпеки типу, оскільки можна передати будь-яке значення. Набрані об'єкти, з іншого боку, нічим не відрізняються від перерахунків з точки зору безпеки типу.
casablanca

Відповіді:


78

Технічно можна переглядати enums як клас із купою набраних констант, і це насправді, як константи enum реалізуються всередині країни. enumОднак використання цих інструментів дає вам корисні методи ( Enum javadoc ), які в іншому випадку вам доведеться реалізовувати самостійно, наприклад Enum.valueOf.


14
Існує також .values()перегляд списку значень.
h3xStream

1
Це здається правильною відповіддю, хоча це не дуже задовольняє. На мій погляд, Java навряд чи варто додати підтримку переліків лише для більш компактного синтаксису та доступу до методів Enum.
Крейг Ш

7
@ Зроби свої інстинкти правильні - це дуже погана відповідь, оскільки вона цілком пропустила мету переліків. Дивіться мої коментарі під запитанням про частину причин.
богем

1
@Bohemian: Я не "пропустив мету" переліків - я їх постійно використовую. Дивіться мою відповідь на ваш коментар вище.
casablanca

1
Метод Enum.valuesOf повертає один і той же об'єкт, якщо його викликають двічі?
Емре Актюрк

104
  1. Тип безпеки та безпека цінності.
  2. Гарантована одиночка.
  3. Можливість визначення та переосмислення методів.
  4. Можливість використання значень у switchоператорах caseзаяви без кваліфікації.
  5. Вбудована секвенціалізація значень через ordinal().
  6. Серіалізація за назвою, а не за значенням, що пропонує ступінь надійності в майбутньому.
  7. EnumSetта EnumMapзаняття.

19
Сказавши все це, кожного разу, коли я вводив код в Enum, я шкодував про це.
Маркіз Лорн

4
Чому ви пошкодували про це? Я ніколи…
glglgl

2
@glglgl Це ставило для програми додавання коду в місце, де я вважав, що він насправді не належить, це було лише визначення набору значень. Якби я мав це зробити ще раз, я включив би його в одне з численних switchтверджень, які були оригінальною мотивацією для використання антитела Enumвзагалі.
Маркіз Лорн

72

Ніхто не згадав про можливість використовувати їх у switchвисловлюваннях; Я теж кину це.

Це дозволяє довільно використовувати складні перерахунки, використовуючи чистий спосіб, не використовуючи instanceofпотенційно заплутані ifпослідовності чи значення перемикання без рядків / int. Канонічний приклад - державна машина.


У будь-якому випадку, ви не згадали жодних переваг від перерахунків проти статичних полів. Ви можете використовувати достатньо типів у операторах переключення зі статичними полями. Op Потрібні реальні функціональні можливості або відмінності у виконанні
Genaut

@Genaut Перевага полягає в тому, що енуми мають більшу функціональність, ніж рядок або int - питання стосувалося відмінностей, які я надавав. ОП вже знає, що таке переслідування, і ніхто більше не згадував заяви про перемикання, коли я розміщував це 4,5 років тому, і, принаймні, декілька людей знайшли, що це дало нову інформацію ¯_ (ツ) _ / ¯
Дейв Ньютон,

44

Основна перевага - безпека типу. За допомогою набору констант можна використовувати будь-яке значення одного і того ж внутрішнього типу, вводячи помилки. З перерахунком можна використовувати лише застосовні значення.

Наприклад

public static final int SIZE_SMALL  = 1;
public static final int SIZE_MEDIUM = 2;
public static final int SIZE_LARGE  = 3;

public void setSize(int newSize) { ... }

obj.setSize(15); // Compiles but likely to fail later

проти

public enum Size { SMALL, MEDIUM, LARGE };

public void setSize(Size s) { ... }

obj.setSize( ? ); // Can't even express the above example with an enum

3
класи також є безпечними ...: / (якщо статичне поле належить до типу контейнерного класу)
h3xStream

Ви все ще можете передати це недійсне значення, але це була б помилка часу компіляції, яку помітити набагато простіше.
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

2
Ви можете зателефонувати setSize(null)у своєму другому прикладі, але він, швидше за все, вийде з ладу набагато швидше, ніж помилка першого прикладу.
Джефрі

42

Менша плутанина. Візьмемо Fontдля прикладу. У ньому є конструктор, який бере назву, яку Fontви хочете, її розмір та стиль ( new Font(String, int, int)). До сьогоднішнього дня я не можу згадати, якщо стиль чи розмір виходять на перше місце. Якщо Fontб використовували enumдля всіх його різних стилів ( PLAIN, BOLD, ITALIC, BOLD_ITALIC), його конструктор буде виглядати Font(String, Style, int), запобігаючи плутанину. На жаль, нас enumне було, коли Fontклас був створений, і оскільки Java повинна підтримувати зворотну сумісність, нас завжди буде мучити ця двозначність.

Звичайно, це лише аргумент для використання enumзамість public static finalконстант. Енуми також ідеально підходять для одиночних клавіш і реалізують поведінку за замовчуванням, одночасно дозволяючи здійснити подальшу налаштування (IE стратегія ). Приклад останнього - це java.nio.file: OpenOptionі StandardOpenOptionякщо розробник захотів створити свій власний нестандартний OpenOption, він міг би.


Ваш випадок "Шрифт" все ще неоднозначний, якщо на нього попросили двох однакових перерахунків. Канонічна відповідь на цю проблему - це те, що багато мов називають іменовані параметри . Підтримка підпису того чи іншого методу IDE.
aaaaaa

1
@aaaaaa Я не бачив багатьох випадків, коли конструктор брав би два однакових enumбез використання varargs або a, Setщоб взяти довільну їх кількість.
Джефрі

@aaaaaa Основна проблема з названими параметрами, вона покладається на деталі реалізації (назва параметра). Я можу зробити інтерфейс interface Foo { public void bar(String baz); }. Хто - то робить клас, що викликає bar: someFoo.bar(baz = "hello");. Я змінюю підпис Foo::barна public void bar(String foobar). Тепер людині, яка зателефонувала someFoo, потрібно буде змінити свій код, якщо він все ще хоче, щоб він працював.
Джефрі

Я також не пам'ятаю, щоб бачити послідовні типи перерахунків, але думав, що спільним може бути DAY_OF_WEEK чи щось подібне. І хороший момент щодо інтерфейсу - не думав про це. Особисто я вирішив би цю проблему через повсюдну неоднозначність, викликану неіменованими параметрами, що потребує більш сильної підтримки IDE. Однак я розумію, що це судження, і що порушення змін API - це те, що слід серйозно розглянути.
аааааа

26

Тут є багато хороших відповідей, але жоден не зазначає, що є високооптимізовані реалізація класів / інтерфейсів API API спеціально для переліків :

Ці перелічені специфічні класи приймають лише Enumекземпляри ( EnumMapєдині приймають Enums лише як ключі), і коли це можливо, вони повертаються до компактного представлення та маніпуляції бітами при їх реалізації.

Що це означає?

Якщо в нашому Enumтипі не більше 64 елементів (більшість Enumприкладів реального життя буде кваліфіковано для цього), реалізація зберігає елементи в одному longзначенні, кожен відповідний Enumекземпляр буде асоційований з частиною цього 64-бітного розміру long. Додавання елемента до елемента - EnumSetце просто встановлення власного біта на 1, видалення його - просто встановлення цього біта до 0. Тестування, чи є елемент у складі, Set- це лише один тест біткої маски! Тепер Enumдля цього треба кохати !


1
Я знав про цих двох раніше, але я просто багато дізнався з вашого What does this mean?розділу. Я знав, що в впровадженні розміру 64 є розбіжність, але я не знав чому
Крістофер Ручінський,

15

приклад:

public class CurrencyDenom {
   public static final int PENNY = 1;
 public static final int NICKLE = 5;
 public static final int DIME = 10;
public static final int QUARTER = 25;}

Обмеження констант java

1) Відсутня безпека типу : по-перше, це не безпечно для типу; Ви можете призначити будь-яке дійсне значення int, наприклад, 99, хоча немає монети, яка б представляла це значення.

2) Немає значущого друку : значення друку будь-якої з цих констант надрукує числове значення замість значущої назви монети, наприклад, коли ви надрукуєте NICKLE, вона надрукує "5" замість "NICKLE"

3) Немає простору імен : для доступу до константи valuDenom нам потрібно встановити префікс ім'я класу, наприклад, CurrencyDenom.PENNY, а не просто використовувати PENNY, хоча це також можна досягти, використовуючи статичний імпорт у JDK 1.5

Перевага енту

1) Енуми на Java є безпечними для типу та мають власний простір імен. Це означає, що ваш перерахунок матиме тип, наприклад, "Валюта" у наведеному нижче прикладі, і ви не можете призначити будь-яке значення, окрім вказаного в константах Enum.

public enum Currency {PENNY, NICKLE, DIME, QUARTER};

Currency coin = Currency.PENNY; coin = 1; //compilation error

2) Enum в Java є типовим типом, наприклад класом або інтерфейсом, і ви можете визначити конструктор, методи та змінні всередині java Enum, що робить його більш потужним, ніж Enum в C і C ++, як показано в наступному прикладі типу Java Enum.

3) Ви можете вказати значення констант enum на час створення, як показано нижче на прикладі: public enum Currency {PENNY (1), NICKLE (5), DIME (10), QUARTER (25)}; Але для цього вам потрібно визначити змінну члена та конструктор, оскільки PENNY (1) насправді викликає конструктор, який приймає значення int, див. Нижче приклад.

public enum Currency {
    PENNY(1), NICKLE(5), DIME(10), QUARTER(25);
    private int value;

    private Currency(int value) {
            this.value = value;
    }
}; 

Довідка: https://javarevisited.blogspot.com/2011/08/enum-in-java-example-tutorial.html


11

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

Ці атрибути переліків допомагають і програмісту, і компілятору. Наприклад, скажімо, ви бачите функцію, яка приймає ціле число. Що може означати це ціле число? Які значення можна передати? Ти насправді не знаєш одразу. Але якщо ви бачите функцію, яка приймає enum, ви дуже добре знаєте всі можливі значення, які можна передати.

Для компілятора перерахунки допомагають визначити діапазон значень, і якщо ви не призначите спеціальним значенням членам перерахування, вони знаходяться в діапазоні від 0 і вище. Це допомагає автоматично відстежувати помилки в коді за допомогою перевірок безпеки та ін. Наприклад, компілятор може попередити вас, що ви не обробляєте всі можливі значення перерахунків у вашому операторі комутатора (тобто коли у вас немаєdefault регістру та обробляєте лише одне з N значень перерахувань). Він також попереджає вас, коли ви перетворюєте довільне ціле число в enum, оскільки діапазон значень enum менший, ніж ціле число, а це, в свою чергу, може викликати помилки у функції, яка насправді не приймає ціле число. Також генерація таблиці стрибків для комутатора стає простішою, коли значення становлять від 0 і вище.

Це стосується не лише Java, але й інших мов, які також мають сувору перевірку типу. C, C ++, D, C # - хороші приклади.


4

Перерахунок неявно остаточний, з приватними конструкторами, усі його значення одного типу або підтипу, ви можете отримати всі його значення, використовуючи values(), отримуючи його name()чи ordinal()значення, або ви можете шукати перерахунок за кількістю чи назвою.

Ви також можете визначити підкласи (хоч і навмисне остаточний, щось не можна зробити іншим способом)

enum Runner implements Runnable {
    HI {
       public void run() {
           System.out.println("Hello");
       }
    }, BYE {
       public void run() {
           System.out.println("Sayonara");
       }
       public String toString() {
           return "good-bye";
       }
    }
 }

 class MYRunner extends Runner // won't compile.

4

Переваги:

  1. Переліки безпечні для типу, статичні поля - ні
  2. Є кінцева кількість значень (неможливо передавати неіснуюче значення перерахунку. Якщо у вас є поля статичного класу, ви можете зробити цю помилку)
  3. Кожен перерахунок може мати декілька властивостей (полів / гетерів), призначених - інкапсуляція. Також кілька простих методів: YEAR.toSeconds () або подібні. Порівняйте: Colors.RED.getHex () з Colors.toHex (Colors.RED)

"наприклад, здатність легко присвоювати елементу enum певне значення"

enum EnumX{
  VAL_1(1),
  VAL_200(200);
  public final int certainValue;
  private X(int certainValue){this.certainValue = certainValue;}
}

"а отже, можливість перетворити ціле число в перерахунок без гідної кількості зусиль" Додайте метод перетворення int в enum, який це робить. Просто додайте статичний HashMap <Integer, EnumX>, що містить відображення java enum .

Якщо ви дійсно хочете перетворити ord = VAL_200.ordinal () назад у val_200, просто використовуйте: EnumX.values ​​() [ord]


3

Ще одна важлива відмінність полягає в тому, що компілятор Java розглядає static finalполя примітивних типів і String як літерали. Це означає, що ці константи стають вбудованими. Це схоже на C/C++ #defineпрепроцесора. Див це так питання . Це не так із перерахунками.



2

Найбільша перевага - enum Singletons - це легко писати та захищати нитки:

public enum EasySingleton{
    INSTANCE;
}

і

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

обидва схожі, і це реалізувало серіалізацію самостійно, реалізуючи

//readResolve to prevent another instance of Singleton
    private Object readResolve(){
        return INSTANCE;
    }

більше


0

Я думаю, що цього enumне може бути final, тому що під компілятором капота генеруються підкласи для кожного enumзапису.

Більше інформації з джерела


Внутрішні вони не є остаточними, тому що - як ви кажете - внутрішньо можна підкласифікувати. Але, на жаль, ви не можете підкласифікувати їх самостійно, наприклад, для розширення його власними значеннями.
glglgl

0

Є багато переваг переліків, які розміщені тут, і я створюю такі переліки прямо зараз, як це було задано у запитанні. Але я маю перерахунок з 5-6 полів.

enum Planet{
EARTH(1000000, 312312321,31232131, "some text", "", 12),
....
other planets
....

У таких випадках, коли у вас є кілька полів в перерахунках, набагато важче зрозуміти, яке значення належить до того поля, як вам потрібно побачити конструктор та око.

Клас із static finalконстантами та використання Builderшаблону для створення таких об'єктів робить його більш читабельним. Але, ви б втратили всі інші переваги використання enum, якщо вони вам потрібні. Одним з недоліків таких класів є те, що вам потрібно додатиPlanet об'єкти вручну до list/setзPlanets.

Я до сих пір вважаю за краще перерахування над таким класом, як values()пригождается і ви ніколи не знаєте , якщо вам потрібні їх використовувати в switchабо EnumSetабо EnumMapв майбутньому :)


0

Основна причина: Енуми допомагають вам написати добре структурований код, де семантичне значення параметрів чітке та сильно набране під час компіляції - з усіх причин, які дали інші відповіді.

Quid pro quo: у Java поза скринькою масив членів Enum остаточний. Це, як правило, добре, оскільки допомагає забезпечити безпеку та тестування, але в деяких ситуаціях це може бути недоліком, наприклад, якщо ви поширюєте існуючий базовий код, можливо, з бібліотеки. На відміну від цього, якщо ті самі дані є у класі зі статичними полями, ви можете легко додати нові екземпляри цього класу під час виконання (можливо, вам також знадобиться написати код, щоб додати їх до будь-якого Iterable, який має для цього класу). Але таку поведінку Enums можна змінити: використовуючи роздуми, ви можете додавати нових членів під час виконання або замінювати наявних членів, хоча це, мабуть, слід робити лише в спеціалізованих ситуаціях, де немає альтернативи: тобто це хакітне рішення і може спричинити несподівані проблеми, дивіться мою відповідь наЧи можу я додавати та видаляти елементи перерахування під час виконання Java .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.