В чем преимущество перечисления Java по сравнению с классом с открытыми статическими полями final?
Я очень знаком с 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);
Насколько я могу судить, ответ Касабланки - единственный, который удовлетворяет этому.
19 ответов
Технически можно действительно рассматривать перечисления как класс с кучей типизированных констант, и именно так константы перечисления реализуются внутри. Используя enum
однако дает вам полезные методы ( Enum Javadoc), которые вы бы в противном случае должны были реализовать самостоятельно, такие как Enum.valueOf
,
- Тип безопасности и ценность безопасности.
- Гарантированный синглтон.
- Возможность определять и переопределять методы.
- Возможность использовать значения в
switch
заявлениеcase
заявления без квалификации. - Встроенная последовательность значений через
ordinal().
- Сериализация по имени, а не по значению, что обеспечивает определенную перспективу.
EnumSet
а такжеEnumMap
классы.
Никто не упомянул о возможности использовать их в switch
заявления; Я добавлю это также.
Это позволяет использовать произвольно сложные перечисления чистым способом без использования instanceof
потенциально сбивает с толку if
последовательности или не строковые / целочисленные значения переключения. Канонический пример - конечный автомат.
Основным преимуществом является безопасность типов. С набором констант может использоваться любое значение того же внутреннего типа, что приводит к ошибкам. С перечислением могут использоваться только применимые значения.
Например
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
Здесь меньше путаницы. принимать 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
"s OpenOption
а также StandardOpenOption
: если разработчик хотел создать свой собственный нестандарт OpenOption
, он мог бы.
Здесь есть много хороших ответов, но никто не упоминает, что существуют высоко оптимизированные реализации классов / интерфейсов API Collection специально для перечислений:
Эти перечисленные конкретные классы принимают только Enum
случаи (EnumMap
только принять Enum
только в качестве ключей), и, когда это возможно, они возвращаются к компактному представлению и манипулированию битами в своей реализации.
Что это значит?
Если наш Enum
тип имеет не более 64 элементов (большая часть реальной Enum
примеры будут соответствовать этому), реализации хранят элементы в одном long
ценность, каждый Enum
рассматриваемый экземпляр будет связан с битом этой 64-битной длины long
, Добавление элемента в EnumSet
просто устанавливает правильный бит в 1, удаляя его просто устанавливает этот бит в 0. Проверка, находится ли элемент в Set
это всего лишь один битовый тест! Теперь ты должен любить Enum
за это!
Пример:
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;
}
};
ссылка: http://javarevisited.blogspot.in/2011/08/enum-in-java-example-tutorial.html
Как вы уже заметили, первое преимущество перечислений - это простота синтаксиса. Но главная задача перечислений - предоставить общеизвестный набор констант, которые по умолчанию образуют диапазон и помогают выполнять более полный анализ кода с помощью проверок безопасности типов и значений.
Эти атрибуты перечислений помогают как программисту, так и компилятору. Например, допустим, вы видите функцию, которая принимает целое число. Что может означать это целое число? Какие ценности вы можете передать? Вы не сразу знаете. Но если вы видите функцию, которая принимает enum, вы очень хорошо знаете все возможные значения, которые вы можете передать.
Для компилятора перечисления помогают определить диапазон значений, и если вы не назначите специальные значения членам перечисления, они находятся в диапазоне от 0 и выше. Это помогает автоматически отслеживать ошибки в коде с помощью проверок безопасности типов и многого другого. Например, компилятор может предупредить вас, что вы не обрабатываете все возможные значения перечисления в вашем операторе switch (т.е. когда у вас нет default
регистр и обрабатывать только одно из N значений enum). Он также предупреждает вас, когда вы преобразуете произвольное целое число в enum, потому что диапазон значений enum меньше целого, и это, в свою очередь, может вызвать ошибки в функции, которая на самом деле не принимает целое число. Кроме того, создание таблицы переходов для коммутатора становится проще, когда значения начинаются с 0 и выше.
Это относится не только к Java, но и к другим языкам со строгой проверкой типов. C, C++, D, C# являются хорошими примерами.
Преимущества enum:
- Перечисления безопасны от типа, статические поля не являются
- Существует конечное число значений (невозможно передать несуществующее значение перечисления. Если у вас есть статические поля класса, вы можете совершить эту ошибку)
- Каждому перечислению может быть присвоено несколько свойств (полей / получателей) - инкапсуляция. Также несколько простых методов: 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, содержащий сопоставление.
Если вы действительно хотите преобразовать ord=VAL_200.ordinal() обратно в val_200, просто используйте: EnumX.values ()[ord]
Перечисление является косвенно конечным, с частными конструкторами, все его значения имеют одинаковый тип или подтип, вы можете получить все его значения, используя 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.
Самое большое преимущество - 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;
}
Другое важное отличие заключается в том, что Java-компилятор обрабатывает static final
поля примитивных типов и String как литералы. Это означает, что эти константы становятся встроенными. Это похоже на C/C++
#define
препроцессор. смотри этот ТАК вопрос. Это не относится к перечислениям.
Вы получаете проверку времени компиляции действительных значений при использовании enum. Посмотри на этот вопрос.
Перечисления могут быть локальными
Начиная с Java 16, перечисление может быть определено локально (внутри метода). Эта область дополняет возможность определять перечисление как вложенный или как отдельный класс.
Эта новая область локального определения появилась вместе с новой функцией записей. Подробнее см. JEP 395: Записи . Перечисления, интерфейсы и записи могут быть определены локально в Java 16+.
Напротив,
public static final
поля всегда имеют глобальную область видимости.
Здесь опубликовано множество преимуществ перечислений, и я создаю такие перечисления прямо сейчас, как задано в вопросе. Но у меня перечисление с 5-6 полями.
enum Planet{
EARTH(1000000, 312312321,31232131, "some text", "", 12),
....
other planets
....
В таких случаях, когда у вас есть несколько полей в перечислениях, очень сложно понять, какое значение принадлежит какому полю, поскольку вам нужно увидеть конструктор и глаз.
Класс с static final
константы и использование Builder
шаблон для создания таких объектов делает его более читабельным. Но вы потеряете все другие преимущества использования перечисления, если они вам понадобятся. Одним из недостатков таких классов является то, что вам нужно добавитьPlanet
объекты вручную в list/set
из Planets.
Я по-прежнему предпочитаю enum такому классу, как values()
пригодится, и вы никогда не знаете, нужны ли они вам в switch
или EnumSet
или EnumMap
в будущем:)
Я думаю enum
не может быть final
потому что под капотом компилятор генерирует подклассы для каждого enum
запись.
Больше информации от источника
Ты можешь сделать :
public enum Size { SMALL(1), MEDIUM(2), LARGE(3) };
private int sizeValue;
Size(sizeValue) {this.sizeValue = value; }
Таким образом, вы можете получить значение размера, подобное этому SMALL.getSizeValue();
Если вы хотите установить размеры, перечисления не для вас, если вы будете определять только константы и фиксированные значения, подойдут.
Проверьте эту ссылку, возможно, она вам поможет
Основная причина: перечисления помогают вам писать хорошо структурированный код, в котором семантическое значение параметров ясно и строго типизировано во время компиляции - по всем причинам, указанным в других ответах.
Услуга за услугу: в Java "из коробки" массив членов Enum является окончательным. Обычно это хорошо, так как помогает оценить безопасность и тестирование, но в некоторых ситуациях это может быть недостатком, например, если вы расширяете существующий базовый код, возможно, из библиотеки. Напротив, если те же данные находятся в классе со статическими полями, вы можете легко добавить новые экземпляры этого класса во время выполнения (вам также может потребоваться написать код, чтобы добавить их к любому Iterable, который у вас есть для этого класса). Но это поведение Enums можно изменить: используя отражение, вы можете добавлять новые элементы во время выполнения или заменять существующие элементы, хотя это, вероятно, следует делать только в особых ситуациях, когда нет альтернативы: то есть это хакерское решение и может вызвать неожиданные проблемы, см. мой ответ на Могу ли я добавлять и удалять элементы перечисления во время выполнения в Java.
Обычно это считается плохой практикой. Проблема заключается в том, что константы являются частью общедоступного "интерфейса" (если не сказать лучшего слова) класса реализации. Это означает, что реализующий класс публикует все эти значения во внешних классах, даже если они требуются только для внутреннего использования. Константы распространяются по всему коду. Примером является интерфейс SwingConstants в Swing, который реализуется десятками классов, которые все "реэкспортируют" все свои константы (даже те, которые они не используют) как свои собственные.
Постоянный шаблон интерфейса - плохое использование интерфейсов. То, что класс использует некоторые константы внутри, является деталью реализации. Реализация постоянного интерфейса вызывает утечку этой детали реализации в экспортируемый API класса. Для пользователей класса не имеет значения, что класс реализует постоянный интерфейс. На самом деле, это может даже запутать их. Хуже того, он представляет собой обязательство: если в будущем выпуске класс будет изменен так, что ему больше не нужно будет использовать константы, он все равно должен реализовать интерфейс для обеспечения двоичной совместимости. Если нефинальный класс реализует постоянный интерфейс, все его подклассы будут иметь свои пространства имен, загрязненные константами в интерфейсе.
Перечисление может быть лучшим подходом. Или вы можете просто поместить константы как открытые статические поля в классе, который не может быть создан. Это позволяет другому классу получать к ним доступ, не загрязняя собственный API.