Я - почти получающий высшее образование студент информатики, и в течение моей карьеры кодирования, я нашел очень немного экземпляров, где перечисления, за исключением канонических случаев, таких как представление поверхностей стандартной деки карт, используются.
Вы знаете о каких-либо умных способах, которыми перечисления используются в повседневном кодировании?
Почему перечисления так важны и в том, какие ситуации нужно смочь определить, что создание перечисления является лучшим подходом?
Это основные аргументы для enum
, EnumMap
и EnumSet
на коротких примерах.
enum
Начиная с Java 6, java.util.Calendar
является примером беспорядочного класса, которому можно было бы извлечь большую пользу из использования enum
(среди прочих улучшений).
В настоящее время Календарь
определяет следующие константы ( среди многих других ):
// int constant antipattern from java.util.Calendar
public static final int JANUARY = 0;
public static final int FEBRUARY = 1;
...
public static final int SUNDAY = 1;
public static final int MONDAY = 2;
...
Все они int
, хотя они, очевидно, представляют разные концептуальные сущности.
Ниже приведены некоторые серьезные последствия:
ПОНЕДЕЛЬНИК = 0;
, ВОСКРЕСЕНЬЕ = 0;
, то у нас будет ПОНЕДЕЛЬНИК == ВОСКРЕСЕНЬЕ
int
:
setMonth (JANUARY)
, но мы также можем setMonth (THURSDAY)
или setMonth (42)
set (int , int, int, int, int, int)
(настоящий метод!) делает! В отличие от этого, у нас могло бы быть что-то вроде этого:
// Hypothetical enums for a Calendar library
enum Month {
JANUARY, FEBRUARY, ...
}
enum DayOfWeek {
SUNDAY, MONDAY, ...
}
Теперь нам больше не нужно беспокоиться о ПОНЕДЕЛЬНИК == ВОСКРЕСЕНЬЕ
(этого никогда не может случиться!), И поскольку Месяц
] и DayOfWeek
- разные типы, setMonth (MONDAY)
не компилируется.
Кроме того, вот несколько кодов до и после:
// BEFORE with int constants
for (int month = JANUARY; month <= DECEMBER; month++) {
...
}
Здесь мы делаем всевозможные предположения, например ЯНВАРЬ + 1 == ФЕВРАЛЬ
и т. Д. С другой стороны, аналог enum
более краток, удобочитаем и делает меньше предположений (и, следовательно, меньше шансов для ошибок):
// AFTER with enum
for (Month month : Month.values()) {
...
}
В Java enum
представляет собой класс
, который имеет много специальных свойств, но, тем не менее, класс
, что позволяет вам при необходимости определите методы и поля экземпляра.
Рассмотрим следующий пример:
// BEFORE: with int constants
public static final int NORTH = 0;
public static final int EAST = 1;
public static final int SOUTH = 2;
public static final int WEST = 3;
public static int degreeFor(int direction) {
return direction * 90; // quite an assumption!
// must be kept in-sync with the int constants!
}
//...
for (int dir = NORTH; dir <= WEST; dir++) {
... degreeFor(dir) ...
}
С другой стороны, с enum
вы можете написать что-то вроде этого:
enum Direction {
NORTH(0), EAST(90), SOUTH(180), WEST(270);
// so obvious! so easy to read! so easy to write! so easy to maintain!
private final int degree;
Direction(int degree) { this.degree = degree; }
public int getDegree() { return degree; }
}
//...
for (Direction dir : Direction.values()) {
... dir.getDegree() ...
}
Рассмотрим следующий пример:
static int apply(int op1, int op2, int operator) {
switch (operator) {
case PLUS : return op1 + op2;
case MINUS : return op1 - op2;
case ...
default: throw new IllegalArgumentException("Unknown operator!");
}
}
Как показано в предыдущем примере, enum
в Java может иметь методы экземпляра, но не только это, но и каждая константа может иметь свой собственный конкретный @Override
. Это показано в следующем коде:
enum Operator {
PLUS { int apply(int op1, int op2) { return op1 + op2; } },
MINUS { int apply(int op1, int op2) { return op1 - op2; } },
...
;
abstract int apply(int op1, int op2);
}
EnumMap
Вот цитата из Эффективное 2-е издание Java :
Никогда не выводите значение, связанное с перечислением
из его
порядкового номера ()
; вместо этого сохраните его в поле экземпляра. ( Правило 31: используйте поля экземпляра вместо порядковых номеров )Редко бывает уместно использовать порядковые номера для индексации массивов: вместо этого используйтеEnumMap
. Общий принцип заключается в том, что прикладные программисты должны редко, если вообще когда-либо, использоватьEnum.ordinal
. ( Элемент 33: ИспользуйтеEnumMap
вместо порядкового индекса )
По сути, где, как и раньше, у вас может быть что-то вроде этого:
// BEFORE, with int constants and array indexing
Employee[] employeeOfTheMonth = ...
employeeOfTheMonth[JANUARY] = jamesBond;
Теперь вы можете иметь:
// AFTER, with enum and EnumMap
Map<Month, Employee> employeeOfTheMonth = ...
employeeOfTheMonth.put(Month.JANUARY, jamesBond);
EnumSet
Степень двойки int
константы часто используются, например в C ++ для обозначения битовых наборов. Это основано на побитовых операциях . Пример может выглядеть примерно так:
public static final int BUTTON_A = 1;
public static final int BUTTON_B = 2;
public static final int BUTTON_X = 4;
public static final int BUTTON_Y = 8;
int buttonState = BUTTON_A | BUTTON_X; // A & X are pressed!
if ((buttonState & BUTTON_B) != 0) { // B is pressed...
...
}
С enum
и EnumSet
это может выглядеть примерно так:
enum Button {
A, B, X, Y;
}
Set<Button> buttonState = EnumSet.of(Button.A, Button.X); // A & X are pressed!
if (buttonState.contains(Button.B)) { // B is pressed...
...
}
ordinal ()
, однако ... EnumSet
! Перечисления идеально подходят для представления состояния / статуса чего-либо. . Я часто использую перечисления, такие как:
public enum PlayerState {
WALKING, STANDING, JUMPING...
}
Всегда необходимо соблюдать баланс между перечислениями и производными классами.Чтобы дать простой пример, рассмотрим класс Cat
. Кошки
имеют разный уровень враждебности. Таким образом, мы могли бы создать производные классы HostileCat
, FriendlyCat
и т. Д. Однако есть также разные типы кошек, Львы
, Тигры
и т. д. Внезапно у нас появляется целая куча Cat
объектов. Таким образом, альтернативным решением является предоставление перечисления Hostility
в классе Cat
, что снижает количество производных классов и общую сложность.
Как предложил Мануэль Селва, прочтите Эффективное второе издание Java , но также прочтите одноэлементный раздел. Использование enum - самый простой способ получить чистый одноэлементный объект .
То, что вы бы определили как константы String
/ int
до Java 1.5, теперь вы должны определить как перечисления. Например, вместо того, чтобы иметь:
public class StatusConstants {
public int ACTIVE = 1;
public int SUSPENDED = 2;
public int TEMPORARY_INACTIVE = 3;
public int NOT_CONFIRMED = 4;
}
Теперь у вас есть более безопасный и удобный для разработчиков:
public enum Status {
ACTIVE, SUSPENDED, TEMPORARY_INACTIVE, NOT_CONFIRMED
}
То же самое касается всего, что имеет более одной опции, например статусы, типы улиц (str., Boul., Rd., и т. д.), уровни доступа пользователей (администратор, модератор, обычный пользователь) и т. д.
Проверьте this для получения дополнительных примеров и пояснений.
Ответ по умолчанию: все разработчики Java должны окончательно прочитать Эффективное второе издание Java . Есть глава о enum
Я думаю, вы говорите о перечислениях, не перечислители.
Перечисления хорошо подходят для любого места в вашем приложении, где вы могли бы иначе использовать «магические строки» или «магические числа».
Легче всего понять их использование в File Access. Каждый режим доступа к файлу (чтение, запись, добавление) представлен в перечислении. Если бы вы использовали «магические числа», у вас могло бы получиться что-то вроде:
public static class Constants
{
public static final int FILEMODE_READ = 1;
public static final int FILEMODE_WRITE = 2;
public static final int FILEMODE_APPEND = 3;
}
Если бы вы могли выразить намерение намного яснее и лаконичнее с помощью перечисления:
public enum FileMode
{
Read,
Write,
Append
}
Рассмотрим простой код:
без перечислений:
int OPT_A_ON = 1;
int OPT_A_OFF = 0;
int OPT_B_ON = 1;
int OPT_B_OFF = 0;
SetOption(OPT_A_ON, OPT_B_OFF); // Declaration: SetOption(int, int)
Выглядит нормально, верно? Вот только SetOptions() сначала хочет получить опцию B, а затем A. Это пройдет через компилятор, но при выполнении установит опции в обратном порядке.
Теперь, с перечислениями:
enum OptA {On =1, Off = 0};
enum OptB {On =1, Off = 0};
SetOption(OptA.On, OptB.Off);// Declaration: SetOption(OptB, OptA)
Теперь мы делаем ту же ошибку, но на этот раз, поскольку перечисления - это разные типы, ошибка будет поймана компилятором.
(ПРИМЕЧАНИЕ: я не совсем программист Java, поэтому прошу простить незначительные синтаксические ошибки)