Перечисления: почему? Когда? - PullRequest
7 голосов
/ 29 июля 2010

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

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

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

Ответы [ 7 ]

29 голосов
/ 29 июля 2010

Это основные аргументы для enum, EnumMap и EnumSet на коротких примерах.

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

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

  • Хрупкие ;необходимо позаботиться о назначении разных номеров при необходимости.
    • Если по ошибке мы установим MONDAY = 0;, SUNDAY = 0;, тогда у нас будет MONDAY == SUNDAY
  • Нет пространства имен и нет безопасности типов , так как все это просто 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, ...
}

Теперь у нас никогда нетбеспокоиться о 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, из его ordinal();вместо этого сохраните его в поле экземпляра.( Элемент 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
    • Элемент 30: используйте enum вместо int константы
    • Элемент 31: Используйте поля экземпляров вместо ординалов
    • Элемент 32: Используйте EnumSet вместо битовых полей
    • Элемент 33: Использовать EnumMap вместо порядкового индексирования

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

7 голосов
/ 29 июля 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
}

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

Проверьте this для получения дополнительных примеров и пояснений..

5 голосов
/ 29 июля 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, поэтому, пожалуйста, прости за незначительные синтаксические ошибки)

3 голосов
/ 29 июля 2010

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

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

Самая простая область для понимания их использования - доступ к файлам.Каждый режим доступа к файлу (чтение, запись, добавление) представлен в перечислении.Если бы вы использовали «магические числа», у вас могло бы быть что-то, похожее на:

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
}
2 голосов
/ 29 июля 2010

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

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

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

2 голосов
/ 29 июля 2010

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

1 голос
/ 29 июля 2010

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

...