Перечисления: Почему? Когда?

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

Вы знаете какие-нибудь хитрые способы использования перечислений в повседневном кодировании?

Почему перечисления так важны и в каких ситуациях следует уметь определять, что построение перечисления является наилучшим подходом?


person danyim    schedule 29.07.2010    source источник
comment
Вы знаете какие-нибудь хитрые способы использования перечислений в повседневном кодировании? - дело не в том, чтобы быть умным; это о том, чтобы код был читаемым, записываемым, обслуживаемым, повторно используемым и т. д. _1 _ / _ 2 _ / _ 3_ combo позволяет все это чудесным образом. Абстракция - это хорошо, а enum - мощная абстракция, особенно в Java, чем в C # / C ++.   -  person polygenelubricants    schedule 29.07.2010
comment
@polygenelubricants Не могли бы вы рассказать мне о практическом примере использования EnumMap / EnumSet?   -  person danyim    schedule 29.07.2010
comment
@polyg: Как я уже упоминал ниже, я не эксперт в Java. Почему перечисления в Java более эффективны, чем C ++ / C #?   -  person James Curran    schedule 29.07.2010
comment
@James: экземпляры перечисления Java - это действительно полноценные объекты, которые могут иметь поля, конструкторы и методы (и вы даже можете переопределить методы по-разному для каждого экземпляра).   -  person Michael Borgwardt    schedule 29.07.2010
comment
+1 для всех, кто любит перечисления так же сильно, как и я.   -  person whiskeysierra    schedule 30.07.2010


Ответы (7)


Это основные аргументы в пользу _1 _, EnumMap и _ 3_ короткими примерами.

Дело для enum

Начиная с Java 6, java.util.Calendar является пример беспорядочного класса, которому можно было бы много выиграть от использования enum (среди других улучшений).

В настоящее время Calendar определяет следующие константы (среди многих других):

// 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, хотя они, очевидно, представляют разные концептуальные сущности.

Ниже приведены некоторые серьезные последствия:

  • It's brittle; care must be taken to assign different numbers whenever needed.
    • If by mistake we set MONDAY = 0;, SUNDAY = 0;, then we have MONDAY == SUNDAY
  • There is no namespace and no type-safety, since everything is just an int:
    • We can setMonth(JANUARY), but we can also setMonth(THURSDAY) or setMonth(42)
    • Кто знает, что такое _ 17_ (настоящий метод!) Работает!

Напротив, вместо этого у нас могло бы быть что-то вроде этого:

// Hypothetical enums for a Calendar library
enum Month {
   JANUARY, FEBRUARY, ...
}
enum DayOfWeek {
   SUNDAY, MONDAY, ...
}

Теперь нам не нужно беспокоиться о MONDAY == SUNDAY (этого никогда не может быть!), А поскольку Month и DayOfWeek относятся к разным типам, setMonth(MONDAY) не компилируется.

Кроме того, вот несколько кодов до и после:

// BEFORE with int constants
for (int month = JANUARY; month <= DECEMBER; month++) {
   ...
}

Здесь мы делаем всевозможные предположения, например JANUARY + 1 == FEBRUARY и т. Д. С другой стороны, enum аналог более лаконичен, удобочитаем и делает меньше предположений (и, следовательно, меньше шансов для ошибок):

// AFTER with enum
for (Month month : Month.values()) {
   ...
}

Случай, например, поля

В Java enum - это class, который имеет много специальных свойств, но, тем не менее, class, позволяющий при необходимости определять методы и поля экземпляра.

Рассмотрим следующий пример:

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

Вот цитата из Effective Java 2nd Edition:

Никогда не извлекайте значение, связанное с enum, из его _ 39_; вместо этого сохраните его в поле экземпляра. (Правило 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...
   ...
}

использованная литература

Смотрите также

  • Effective Java 2nd Edition
    • Item 30: Use enum instead of int constants
    • Правило 31: используйте поля экземпляра вместо порядковых номеров
    • Правило 32: Используйте EnumSet вместо bit поля
    • Правило 33: Используйте EnumMap вместо порядкового номера индексация

Связанные вопросы

person polygenelubricants    schedule 29.07.2010
comment
Я как бы поторопился с этим ближе к концу; Я пересмотрю этот ответ на основе отзывов, если другие сочтут, что есть некоторые части, которые нуждаются в улучшении. - person polygenelubricants; 29.07.2010
comment
Теперь это принято в качестве принятого ответа на этот вопрос. Ваш пост краток и был именно тем, что я искал. Спасибо. - person danyim; 29.07.2010
comment
В вашем случае поля экземпляра, я полагаю, вы хотели написать case MINUS : return op1 - op2; (раньше у вас был оператор +) - person danyim; 29.07.2010
comment
@danyim: Да, спасибо, что уловили это. Я исправлю это завтра вместе с другими правками и улучшениями, о которых я могу подумать. - person polygenelubricants; 29.07.2010

То, что вы определяли как константы 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
}

То же самое касается всего, что имеет более одной опции, например статусы, типы улиц (улица, бульвар, улица и т. Д.), Уровни доступа пользователей (администратор, модератор, обычный пользователь) и т. Д. И т. Д.

Дополнительные примеры и пояснения см. В this.

person Bozho    schedule 29.07.2010
comment
Разве это не единичный случай, когда у меня есть класс, состоящий только из определяющих констант? - person danyim; 29.07.2010

Рассмотрим простой код:

без перечислений:

 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, поэтому прошу прощения за незначительные синтаксические ошибки)

person James Curran    schedule 29.07.2010
comment
Синтаксис перечисления в вашем примере отключен (и это не просто незначительная синтаксическая ошибка): enum значения в Java являются полноценными объектами, а не просто причудливым ярлыком для целых чисел, поэтому вы не можете просто определить их целочисленные значения таким образом. Просто удалите это назначение, и ваш код будет правильным. - person Joachim Sauer; 11.04.2011

Я думаю, вы говорите о перечислениях, а не о перечислителях.

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

Проще всего понять их использование в 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;
}

Когда вы могли выразить намерение намного яснее и лаконичнее с помощью Enumeration:

public enum FileMode
{
    Read,
    Write,
    Append
}
person Justin Niessner    schedule 29.07.2010

Ответ по умолчанию: все разработчики Java должны обязательно прочитать Эффективное второе издание Java. Есть глава о enum

person Manuel Selva    schedule 29.07.2010
comment
Просмотрите доклад Джоша Блоха «Эффективная перезагрузка Java» от Devoxx'08 parleys.com/d/1411. Он отлично освещает важность использования перечислений в различных сценариях. К сожалению, в видео отсутствуют многие слайды, но разговор отличный. - person Alain O'Dea; 29.07.2010

Перечисления идеально подходят для представления состояния / статуса чего-либо. Я часто использую перечисления, такие как:

public enum PlayerState {
    WALKING, STANDING, JUMPING...
}

Всегда необходимо соблюдать баланс между перечислениями и производными классами. Чтобы дать простой пример этого, рассмотрим класс Cat. Cats имеют разный уровень враждебности. Итак, мы могли бы создать производные классы HostileCat, FriendlyCat и т. Д. Однако есть также разные типы кошек, Lions, Tigers и т. Д. Неожиданно у нас есть целый набор Cat Объектов. Таким образом, альтернативным решением является предоставление перечисления Hostility в классе Cat, которое уменьшает количество производных классов и общую сложность.

person Justin Ardini    schedule 29.07.2010
comment
Спасибо за ваш вклад. Этот пример действительно помог мне понять, что вы имели в виду под балансом. - person danyim; 29.07.2010

Как предложил Мануэль Сельва, прочтите Второе эффективное издание Java, но также прочтите раздел синглтонов. Использование перечисления - это самый простой способ получить чистый одноэлементный объект.

person nanda    schedule 29.07.2010
comment
Вчера я впервые использовал синглтон enum. Сохраняет множество подверженных ошибкам шаблонов. - person Alain O'Dea; 29.07.2010