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

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

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

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

7
задан danyim 11 April 2011 в 11:27
поделиться

7 ответов

Это основные аргументы для 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; , то у нас будет ПОНЕДЕЛЬНИК == ВОСКРЕСЕНЬЕ
  • Нет ни пространства имен, ни типа -safety , поскольку все это просто 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);

Случай for 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...
   ...
}

Ссылки

См. также

  • Эффективное Java 2-е издание
    • Пункт 30: Используйте enum вместо int констант
    • Пункт 31: Используйте поля экземпляра вместо порядковых
    • Элемент 32: Используйте EnumSet вместо битовых полей
    • Элемент 33: Используйте EnumMap вместо порядкового индекса

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

29
ответ дан 6 December 2019 в 04:51
поделиться

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

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

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

2
ответ дан 6 December 2019 в 04:51
поделиться

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

1
ответ дан 6 December 2019 в 04:51
поделиться

То, что вы бы определили как константы 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 для получения дополнительных примеров и пояснений.

7
ответ дан 6 December 2019 в 04:51
поделиться

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

2
ответ дан 6 December 2019 в 04:51
поделиться

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

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

Легче всего понять их использование в 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
}
3
ответ дан 6 December 2019 в 04:51
поделиться

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

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

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

5
ответ дан 6 December 2019 в 04:51
поделиться
Другие вопросы по тегам:

Похожие вопросы: