В чем преимущество перечисления Java по сравнению с классом с открытыми статическими полями final?

147

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

Итак, мой вопрос заключается в следующем: есть ли какая-либо польза для перечислений Java по сравнению с классом с множеством открытых статических полей final? Или это просто обеспечивает более компактный синтаксис?

РЕДАКТИРОВАТЬ: Позвольте мне быть более ясным. В чем преимущество перечислений Java над классом с набором открытых статических полей final одного типа ? Например, в примере с планетами в первой ссылке, в чем преимущество перечисления перед классом с этими открытыми константами:

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

Насколько я могу судить, ответ Касабланки - единственный, который удовлетворяет это.

Крейг В
источник
4
@ Bohemian: Это не может быть дубликатом, так как OP только упоминает public static finalполя, которые могут быть типизированными значениями и не обязательно ints.
Касабланка
1
@ Shahzeb Вряд ли. Ясно, что использование перечислений вместо строковых констант ЧРЕЗВЫЧАЙНО хорошая идея и более чем поощряется. Безопасность типов, не требуются статические служебные функции и т. Д. Абсолютно нет причин использовать строки вместо этого.
Во
1
@ Voo Да, я знал, что будут разногласия. И вот один за 49 секунд. Перечисления великолепны (и я люблю их и использую их очень часто), но какой тип безопасности вам нужен при объявлении константы или ее использовании. Создавать enum излишне, когда вам нужно объявить константу для строкового литерала.
Шахзеб,
4
@Shahzeb Если у вас есть одна переменная, обязательно используйте строку, здесь мало что может произойти (одно значение довольно бессмысленно в качестве параметра). Но обратите внимание , что мы говорим о постоянной S, так что теперь мы говорим о , вероятно , передавая их в функции и т.д. У нас нужно вводить-Техника безопасности? Ну, нет, но тогда большинство людей считают, что типы в стиле c "все это void*" хороший стиль, и это может остановить ошибки (особенно если передается более одного параметра enum / string!). Кроме того, он помещает константы в их собственное пространство имен и т. Д. В противоположность этому, просто иметь простые переменные не дает реального преимущества.
Во
3
@ Bohemian: я не вижу как. С ints нет безопасности типов, потому что можно передать любое значение. Типизированные объекты, с другой стороны, ничем не отличаются от перечислений с точки зрения безопасности типов.
Касабланка

Ответы:

78

Технически можно действительно рассматривать перечисления как класс с кучей типизированных констант, и именно так константы перечисления реализуются внутри. Использование enumоднако дает вам полезные методы ( Enum javadoc ), которые в противном случае вам бы пришлось реализовать самостоятельно, такие как Enum.valueOf.

Касабланка
источник
14
Также есть .values()возможность перебирать список значений.
h3xStream
1
Это кажется правильным ответом, хотя и не очень удовлетворительным. По моему мнению, для Java едва ли стоило добавлять поддержку перечислений только для более компактного синтаксиса и доступа к методам Enum.
Крейг W
7
@ Крейг, ваши инстинкты правы - это очень плохой ответ, потому что он полностью пропустил цель перечислений. Посмотрите мои комментарии под вопросом для части причин почему.
Богемский
1
@ Bohemian: я не "пропустил цель" перечислений - я использую их все время. Смотрите мой ответ на ваш комментарий выше.
Касабланка
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последовательностей или значений переключения не-string / int. Канонический пример - конечный автомат.

Дэйв Ньютон
источник
В любом случае, вы не упомянули никаких преимуществ от перечислений по сравнению со статическими полями. Вы можете использовать достаточно типов в операторах switch со статическими полями. 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констант. Перечисления также идеально подходят для одиночных игр и реализации поведения по умолчанию, допуская последующую настройку (например , шаблон стратегии ). Примером последнего является java.nio.file' OpenOptionи StandardOpenOption: если разработчик хотел создать свой собственный нестандарт OpenOption, он мог бы.

Джеффри
источник
Ваш случай «Шрифт» все еще неоднозначен, если запрашивать два одинаковых перечисления. Каноническим ответом на эту проблему является то, что многие языки называют именованными параметрами . Это или лучше поддержка подписи метода IDE.
аааааа
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 Collection специально для перечислений :

Эти специфичные для перечисления классы принимают только Enumэкземпляры ( EnumMapединственные принимают Enumтолько в качестве ключей), и, когда это возможно, они возвращаются к компактному представлению и манипулированию битами в своей реализации.

Что это значит?

Если наш Enumтип имеет не более 64 элементов (большинство реальных Enumпримеров подойдут для этого), реализации сохранят элементы в одном longзначении, каждый Enumрассматриваемый экземпляр будет связан с битом этой 64-битной длины long. Добавление элемента в a EnumSet- это просто установка правильного бита в 1, удаление - просто установка этого бита в 0. Проверка наличия элемента - Setэто всего лишь один тест битовой маски! Теперь ты должен любить тебя Enumза это!

icza
источник
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) Нет пространства имен : чтобы получить доступ к константе currencyDenom, нам нужно добавить префикс имени класса, например CurrencyDenom.PENNY, а не просто использовать PENNY, хотя это также может быть достигнуто с помощью статического импорта в JDK 1.5.

Преимущество enum

1) Перечисления в Java являются типобезопасными и имеют собственное пространство имен. Это означает, что ваше перечисление будет иметь тип, например, «Валюта» в приведенном ниже примере, и вы не можете назначать никакие значения, кроме указанных в константах перечисления.

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 и выше. Это помогает автоматически отслеживать ошибки в коде с помощью проверок безопасности типов и многого другого. Например, компилятор может предупредить вас, что вы не обрабатываете все возможные значения перечисления в вашем операторе switch (то есть, когда у вас нет 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

Преимущества enum:

  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 в перечисление, что делает это. Просто добавьте статический HashMap <Integer, EnumX>, содержащий сопоставление java enum .

Если вы действительно хотите преобразовать ord = VAL_200.ordinal () обратно в val_200, просто используйте: EnumX.values ​​() [ord]

mabn
источник
3

Другое важное отличие состоит в том, что компилятор Java обрабатывает static finalполя примитивных типов и String как литералы. Это означает, что эти константы становятся встроенными. Это похоже на C/C++ #defineпрепроцессор. Смотрите этот ТАК вопрос . Это не относится к перечислениям.

Сейед Джалал Хоссейни
источник
2

Вы получаете проверку времени компиляции действительных значений при использовании перечисления. Посмотри на этот вопрос.

zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
источник
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;
    }

Больше

Premraj
источник
0

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

Больше информации от источника

Sitansu
источник
Внутренне они не являются окончательными, потому что, как вы говорите, могут быть внутренне разделены на подклассы. Но, увы, вы не можете подклассировать их самостоятельно, например, для расширения их собственными ценностями.
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 .

radfast
источник