Перечисления

Механизм перечислений был добавлен в JDK 5 версии, он позволяет удобно хранить и работать с так называемыми "категориальными данными".

Категориальные данные - это данные с ограниченным числом уникальных значений или категорий. Приведем примеры категориальных данных:

  • месяцы года (12 значений: январь, февраль и так далее);

  • пол (мужской, женский, андроген, бесполый и так далее);

  • вероисповедание (православие, католицизм, ислам и так далее);

  • день недели (понедельник, вторник, среда и так далее).

Рассмотрим пример. Для информационной системы «Электронный институт» необходимо создать класс сотрудника кафедры «DepartmentMember», в котором, среди прочих атрибутов, есть атрибут «должность» (position). С целью упрощения, предположим, что сотрудники кафедры могут занимать одну из следующих должностей:

  • «Инженер» (Engineer);

  • «Ассистент» (Assistant);

  • «Старший преподаватель» (Lecturer);

  • «Доцент» (Senior Lecturer);

  • «Профессор» (Professor).

Каким образом закодировать данное значение? Самый первый и очевидный вариант - просто использовать тип данных String.

public class DepartmentMember {

    private String firstName;
    private String lastName;
    private String position;

    public DepartmentMember
            (String firstName, String lastName, String position) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.position = position;
    }
}

Данное решение имеет несколько очень серьезных недостатков. Вы никак не можете обеспечить корректность указания должностей и контролировать создание нового объекта класса.

public static void main(String[] args) {

    DepartmentMember member1 = new DepartmentMember(
            "Иван",
            "Иванов",
            "доцент"
    );

    DepartmentMember member2 = new DepartmentMember(
            "Иван",
            "Иванов",
            "старший преподаватель"
    );
}

Должность старшего преподавателя, например, может быть записана как "ст. преп.", "ст. преподаватель", "ст. пр." и так далее, не говоря про возможные орфографические ошибки. С точки зрения формальной логики, это все разные категории должностей и вы не сможете группировать или фильтровать сотрудников кафедры по полю "должность".

Другой вариант - это создать несколько статических целочисленных констант, которые можно использовать вместо строк.

class DepartmentMember {

    public static final int ENGINEER = 0;
    public static final int ASSISTANT = 1;
    public static final int LECTURER = 2;
    public static final int SENIOR_LECTURER = 3;
    public static final int PROFESSOR = 4;

    private String firstName;
    private String lastName;
    private int position;

    public DepartmentMember
            (String firstName, String lastName, int position) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.position = position;
    }
}

Теперь мы сможем использовать именованные константы вместо строго фиксированных строк.

public static void main(String[] args) {

    DepartmentMember member1 = new DepartmentMember(
            "Иван",
            "Иванов",
            DepartmentMember.ENGINEER
    );

    DepartmentMember member2 = new DepartmentMember(
            "Петр",
            "Петров",
            DepartmentMember.PROFESSOR
    );
}

Использование именованных констант позволяет отчасти избавиться от недостатков предыдущего подхода, но и этот вариант не является приемлемым. Ничто не мешает пользователю класса при создании объекта указать любое целое числа вместо именованной константы, осуществлять проверку ввода – достаточно трудоемкая задача, если в классе предусмотрено более одного категориального значения будет тяжело определять какие конкретно именованные константы необходимо использовать. К тому же, при использовании конструктора или сеттера поля «должность», нет никакого указания на необходимость использования именованных констант, программист сам должен догадаться о наличии нужных констант или читать сопроводительную документацию.

class DepartmentMember {

    public static final int ENGINEER = 0;
    public static final int ASSISTANT = 1;
    public static final int LECTURER = 2;
    public static final int SENIOR_LECTURER = 3;
    public static final int PROFESSOR = 4;

    public static final int FULL_TIME = 100;
    public static final int PART_TIME = 101;

    private String firstName;
    private String lastName;
    private int position;
    private int contractType;

    public DepartmentMember
            (String firstName, String lastName, int position, int contractType) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.position = position;
        this.contractType = contractType;
    }
}
public static void main(String[] args) {

    DepartmentMember member1 = new DepartmentMember(
            "Петр",
            "Петров",
            DepartmentMember.FULL_TIME, // ???
            DepartmentMember.PART_TIME  // ???
    );
}

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

Корректным вариантом решения данной проблемы является использование специального механизма перечислений (enumeration). По сути, перечисление – это тип, ссылочная переменная которого может принимать одно из нескольких заранее определенных значений. Реализуем класс «DepartmentMember» с помощью механизма перечислений:

class DepartmentMember {

    private String firstName;
    private String lastName;
    private Position position;

    public DepartmentMember
            (String firstName, String lastName, Position position) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.position = position;
    }

    public enum Position {
        Engineer, Assistant, Lecturer, SeniorLecturer, Professor;
    }
}
public static void main(String[] args) {

    DepartmentMember member1 = new DepartmentMember(
            "Иван",
            "Иванов",
            DepartmentMember.Position.Assistant
    );

    DepartmentMember member2 = new DepartmentMember(
            "Петр",
            "Петров",
            DepartmentMember.Position.Professor
    );
}

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

Обратите внимание на синтаксис перечисления. Перечисление является классом, но очень своеобразным. Вместо ключевого слова class используется ключевое слово enum (от слова enumeration – перечисление). Вместо полей и методов у перечисления идет просто набор констант через запятую.

Как уже было сказано, перечисление – это класс, а это значит, что он может иметь методы, и мы можем инкапсулировать перечисление и операции работы с ним в одной оболочке.

enum Palette {

    RED("#ff0000"),
    YELLOW("#ffff00"),
    CYAN("#00ffff"),
    GREEN("#008000");

    private final String color;

    Palette(String color) {
        this.color = color;
    }
}
class Controller implements Initializable {
    @FXML Button button1;
    @FXML Button button2;
    @FXML Button button3;
    @FXML Button button4;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        button1.setStyle("-fx-background-color: " + Palette.CYAN);
        button2.setStyle("-fx-background-color: " + Palette.GREEN);
        button3.setStyle("-fx-background-color: " + Palette.YELLOW);
        button4.setStyle("-fx-background-color: " + Palette.RED);
    }
}

В данном примере, экземпляр перечисления хранит строку с цветом.

Last updated