Что такое перечисления и чем они полезны? - PullRequest
457 голосов
/ 17 января 2011

Сегодня я просматривал некоторые вопросы на этом сайте и обнаружил упоминание о enum , используемом в шаблоне синглтона , о предполагаемых преимуществах безопасности потоков для такого решения.Я никогда не использовал enum с, и я программирую на Java уже более пары лет.И, видимо, они сильно изменились.Теперь они даже полностью поддерживают ООП внутри себя.

Теперь, почему и зачем мне использовать enum в повседневном программировании?

Ответы [ 24 ]

597 голосов
/ 17 января 2011

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

Если вы используете перечисления вместо целых чисел (или строковых кодов), вы увеличиваете проверку во время компиляции и избегаете ошибок при передаче недопустимых констант, а также документируете, какие значения допустимы для использования.

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

Как пример, что лучше?

/** Counts number of foobangs.
 * @param type Type of foobangs to count. Can be 1=green foobangs,
 * 2=wrinkled foobangs, 3=sweet foobangs, 0=all types.
 * @return number of foobangs of type
 */
public int countFoobangs(int type)

против

/** Types of foobangs. */
public enum FB_TYPE {
 GREEN, WRINKLED, SWEET, 
 /** special type for all types combined */
 ALL;
}

/** Counts number of foobangs.
 * @param type Type of foobangs to count
 * @return number of foobangs of type
 */
public int countFoobangs(FB_TYPE type)

Вызов метода, подобный:

int sweetFoobangCount = countFoobangs(3);

затем становится:

int sweetFoobangCount = countFoobangs(FB_TYPE.SWEET);

Во втором примере сразу ясно, какие типы разрешены, документы и реализация не могут быть синхронизированы, и компилятор может применить это. Также недопустимый звонок типа

int sweetFoobangCount = countFoobangs(99);

больше не возможно.

128 голосов
/ 26 февраля 2013

Зачем использовать какую-либо функцию языка программирования? Причина, по которой у нас есть языки, -

  1. Программисты эффективно и правильно выражают алгоритмы в форме, которую могут использовать компьютеры.
  2. Сопровождающие понимают алгоритмы, написанные другими, и правильно вносят изменения.

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

public class Color {
    private Color() {} // Prevent others from making colors.
    public static final Color RED = new Color();
    public static final Color AMBER = new Color();
    public static final Color GREEN = new Color();
}

Теперь вы можете написать:

Color trafficLightColor = Color.RED;

Приведенный выше шаблон имеет тот же эффект, что и

public enum Color { RED, AMBER, GREEN };

Оба предоставляют одинаковый уровень проверки справки от компилятора. Boilerplate просто больше печатать. Но сохранение большого количества печатного текста делает программиста более эффективным (см. 1), так что это полезная функция.

Стоит хотя бы еще по одной причине:

Переключение операторов

Одна вещь, которую приведенное выше моделирование перечисления static final делает не , дает вам хорошие switch случаи. Для перечислимых типов переключатель Java использует тип своей переменной, чтобы вывести область применения перечислений, поэтому для приведенного выше enum Color нужно просто сказать:

Color color = ... ;
switch (color) {
    case RED:
        ...
        break;
}

Обратите внимание, что это не Color.RED в случаях. Если вы не используете enum, единственный способ использовать именованные величины с switch - это что-то вроде:

public Class Color {
    public static final int RED = 0;
    public static final int AMBER = 1;
    public static final int GREEN = 2;
}

Но теперь переменная для хранения цвета должна иметь тип int. Хорошая проверка перечисления и симуляции static final завершена. Не счастлив.

Компромисс состоит в использовании скалярного члена в симуляции:

public class Color {
    public static final int RED_TAG = 1;
    public static final int AMBER_TAG = 2;
    public static final int GREEN_TAG = 3;

    public final int tag;

    private Color(int tag) { this.tag = tag; } 
    public static final Color RED = new Color(RED_TAG);
    public static final Color AMBER = new Color(AMBER_TAG);
    public static final Color GREEN = new Color(GREEN_TAG);
}

Сейчас:

Color color = ... ;
switch (color.tag) {
    case Color.RED_TAG:
        ...
        break;
}

Но обратите внимание, еще больше шаблонов!

Использование перечисления в качестве единственного числа

Из приведенного выше шаблона видно, почему перечисление предоставляет способ реализации синглтона. Вместо записи:

public class SingletonClass {
    public static final void INSTANCE = new SingletonClass();
    private SingletonClass() {}

    // all the methods and instance data for the class here
}

и затем доступ к нему с помощью

SingletonClass.INSTANCE

мы можем просто сказать

public enum SingletonClass {
    INSTANCE;

    // all the methods and instance data for the class here
}

, что дает нам то же самое. Мы можем обойтись без этого, потому что Java перечисления реализованы как полные классы с небольшим количеством синтаксического сахара, разбросанного сверху. Это опять-таки менее шаблонно, но не очевидно, если идиома вам не знакома. Мне также не нравится тот факт, что вы получаете различные функции перечисления, даже если они не имеют особого смысла для синглтона: ord и values и т. Д. (На самом деле существует более сложная симуляция, в которой Color extends Integer будет работать с переключателем , но это настолько сложно, что еще более ясно показывает, почему enum - лучшая идея.)

Безопасность потоков

Потоковая безопасность является потенциальной проблемой, только когда синглтоны создаются лениво без блокировки.

public class SingletonClass {
    private static SingletonClass INSTANCE;
    private SingletonClass() {}
    public SingletonClass getInstance() {
        if (INSTANCE == null) INSTANCE = new SingletonClass();
        return INSTANCE;
    }

    // all the methods and instance data for the class here
}

Если многие потоки вызывают getInstance одновременно, в то время как INSTANCE по-прежнему равен нулю, может быть создано любое количество экземпляров. Это плохо. Единственное решение - добавить synchronized доступ для защиты переменной INSTANCE.

Однако приведенный выше код static final не имеет этой проблемы. Он создает экземпляр с нетерпением во время загрузки класса. Загрузка классов синхронизирована.

Синглтон enum фактически ленив, потому что он не инициализируется до первого использования. Инициализация Java также синхронизируется, поэтому несколько потоков не могут инициализировать более одного экземпляра INSTANCE. Вы получаете лениво инициализированный синглтон с очень небольшим кодом. Единственный минус - довольно туманный синтаксис. Вам нужно знать идиому или полностью понимать, как работают загрузка и инициализация класса, чтобы знать, что происходит.

41 голосов
/ 26 февраля 2013

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

  1. Наличие кода, в котором находятся данные (то есть внутри самого перечисления - или часто внутри констант перечисления, которые могут переопределять методы).
  2. Реализация интерфейса (или более), чтобы не привязывать код клиента к перечислению (который должен обеспечивать только набор реализаций по умолчанию).

Простейшим примером будет набор Comparator реализаций:

enum StringComparator implements Comparator<String> {
    NATURAL {
        @Override
        public int compare(String s1, String s2) {
            return s1.compareTo(s2);
        }
    },
    REVERSE {
        @Override
        public int compare(String s1, String s2) {
            return NATURAL.compare(s2, s1);
        }
    },
    LENGTH {
        @Override
        public int compare(String s1, String s2) {
            return new Integer(s1.length()).compareTo(s2.length());
        }
    };
}

Этот «шаблон» может использоваться в гораздо более сложных сценариях, широко использующих все вкусности, которые поставляются с enum: перебирая экземпляры, полагаясь на их неявный порядок, извлекая экземпляр по его имени, статические методы обеспечивают правильный экземпляр для определенных контекстов и т. д. И все же у вас все это скрыто за интерфейсом, поэтому ваш код будет работать с пользовательскими реализациями без изменений, если вам нужно что-то, что недоступно среди «параметров по умолчанию».

Я видел, как это успешно применялось для моделирования концепции гранулярности времени (ежедневно, еженедельно и т. Д.), Когда вся логика была инкапсулирована в перечисление (выбор правильной гранулярности для данного временного диапазона, определенного поведения, связанного с каждым гранулярность как постоянные методы и т. д.). И все же, Granularity с точки зрения сервисного уровня был просто интерфейсом.

32 голосов
/ 17 января 2011

То, что ни один из других ответов не охватил, что делает перечисления особенно мощными, - это возможность иметь шаблонные методы . Методы могут быть частью базового перечисления и переопределяться каждым типом. А благодаря поведению, связанному с перечислением, это часто устраняет необходимость в конструкциях if-else или операторах switch, поскольку в этом блоге демонстрируется - где enum.method() делает то, что первоначально было бы выполнено внутри условного выражения. В этом же примере показано использование статического импорта с перечислениями, а также создание более чистого кода, подобного DSL.

Некоторые другие интересные качества включают в себя тот факт, что перечисления обеспечивают реализацию для equals(), toString() и hashCode() и реализуют Serializable и Comparable.

Для полного изложения всего, что могут предложить перечисления, я настоятельно рекомендую Thinking in Java 4-е издание Брюса Экеля , в котором этой главе посвящена целая глава. Особенно показательны примеры игр типа «камень, бумага, ножницы» (т. Е. RoShamBo) в качестве перечислений.

21 голосов
/ 17 января 2011

Из Java документы -

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

Типичным примером является замена класса набором частных статических конечных int-констант (в пределах разумного числа констант) типом enum. По сути, если вы думаете, что знаете все возможные значения «что-то» во время компиляции, вы можете представить это как тип enum. Перечисления обеспечивают удобочитаемость и гибкость в классе с константами.

Несколько других преимуществ, которые я могу придумать для типов enum. Они всегда являются одним экземпляром определенного класса enum (отсюда и концепция использования enum по мере появления синглтона). Другое преимущество заключается в том, что вы можете использовать перечисления в качестве типа в выражении switch-case. Также вы можете использовать toString () в перечислении, чтобы распечатать их как читаемые строки.

18 голосов
/ 17 января 2011

Теперь, почему и для чего я должен использовать enum в повседневном программировании?

Вы можете использовать Enum для представления небольшого фиксированного набора констант или режима внутреннего класса при увеличениичитаемость.Кроме того, Enums может обеспечить определенную жесткость при использовании в параметрах метода.Они предлагают интересную возможность передачи информации конструктору, как в примере Planets на сайте Oracle , и, как вы обнаружили, также позволяют простой способ создания одноэлементного шаблона.

например: Locale.setDefault(Locale.US) читает лучше, чем Locale.setDefault(1) и обеспечивает использование фиксированного набора значений, показанного в IDE, когда вы добавляете разделитель . вместо всех целых чисел.

13 голосов
/ 25 февраля 2013

Enum s перечисляют фиксированный набор значений в самодокументируемой форме.Они делают ваш код более явным и менее подверженным ошибкам.

Почему бы не использовать String или int вместо Enum для констант?

  1. Компилятор не допустит опечаток , ни значений из фиксированного набора, поскольку перечисления сами по себе являются типами .Последствия:
    • Вам не нужно будет писать предварительное условие (или руководство if), чтобы убедиться, что ваш аргумент находится в допустимом диапазоне.
    • инвариант типа поставляется бесплатно.
  2. Перечисления могут иметь поведение, как и любой другой класс.
  3. Возможно, вам потребуется такой же объем памятив любом случае использовать String s (это зависит от сложности Enum).

Более того, каждый из экземпляров Enum является классом, для которого вы можете определитьего индивидуальное поведение.

Кроме того, они гарантируют безопасность потоков при создании экземпляров (при загрузке перечисления), что является отличным приложением для упрощения Singleton Pattern .

Этот блог иллюстрирует некоторые его приложения, такие как State Machine для синтаксического анализатора.

12 голосов
/ 22 июля 2016

Полезно знать, что enums аналогичны другим классам с полями Constant и private constructor.

Например,

public enum Weekday
{
  MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
} 

Компилятор компилируетсяэто следующим образом;

class Weekday extends Enum
{
  public static final Weekday MONDAY  = new Weekday( "MONDAY",   0 );
  public static final Weekday TUESDAY = new Weekday( "TUESDAY ", 1 );
  public static final Weekday WEDNESDAY= new Weekday( "WEDNESDAY", 2 );
  public static final Weekday THURSDAY= new Weekday( "THURSDAY", 3 );
  public static final Weekday FRIDAY= new Weekday( "FRIDAY", 4 );
  public static final Weekday SATURDAY= new Weekday( "SATURDAY", 5 );
  public static final Weekday SUNDAY= new Weekday( "SUNDAY", 6 );

  private Weekday( String s, int i )
  {
    super( s, i );
  }

  // other methods...
}
11 голосов
/ 31 июля 2015

enum означает enum eration, то есть упоминание (несколько вещей) по одному.

enum - это тип данных, который содержитфиксированный набор констант.

ИЛИ

enum подобен class, с фиксированным набором экземпляров, известных во время компиляции.

Например:

public class EnumExample {
    interface SeasonInt {
        String seasonDuration();
    }

    private enum Season implements SeasonInt {
        // except the enum constants remaining code looks same as class
        // enum constants are implicitly public static final we have used all caps to specify them like Constants in Java
        WINTER(88, "DEC - FEB"), SPRING(92, "MAR - JUN"), SUMMER(91, "JUN - AUG"), FALL(90, "SEP - NOV");

        private int days;
        private String months;

        Season(int days, String months) { // note: constructor is by default private 
            this.days = days;
            this.months = months;
        }

        @Override
        public String seasonDuration() {
            return this+" -> "+this.days + "days,   " + this.months+" months";
        }

    }
    public static void main(String[] args) {
        System.out.println(Season.SPRING.seasonDuration());
        for (Season season : Season.values()){
            System.out.println(season.seasonDuration());
        }

    }
}

Преимущества enum:

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

для more

8 голосов
/ 25 февраля 2013

Помимо всего сказанного другими. В более старом проекте, на который я раньше работал, во многих общениях между сущностями (независимыми приложениями) использовались целые числа, которые представляли собой небольшой набор.Было полезно объявить набор как enum со статическими методами, чтобы получить enum объект из value и наоборот.Код выглядел чище, удобство использования регистра и упрощенная запись в журналы.

enum ProtocolType {
    TCP_IP (1, "Transmission Control Protocol"), 
    IP (2, "Internet Protocol"), 
    UDP (3, "User Datagram Protocol");

    public int code;
    public String name;

    private ProtocolType(int code, String name) {
        this.code = code;
        this.name = name;
    }

    public static ProtocolType fromInt(int code) {
    switch(code) {
    case 1:
        return TCP_IP;
    case 2:
        return IP;
    case 3:
        return UDP;
    }

    // we had some exception handling for this
    // as the contract for these was between 2 independent applications
    // liable to change between versions (mostly adding new stuff)
    // but keeping it simple here.
    return null;
    }
}

Создание enum объекта из полученных значений (например, 1,2) с использованием ProtocolType.fromInt(2) Запись в журналы с использованием myEnumObj.name

Надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...