Перечисления (enum)

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

Итак, что же такое перечисление и зачем они нужны? В самом простом случае – это список констант для общего использования в рамках проекта. Например, это могут быть коды ошибок, или номера клавиш, или типы геометрических фигур и т.д. Главное, что с помощью перечислений достаточно удобно объявлять группы констант, логически (по смыслу) связанных между собой.

Чтобы задать перечисления в Java используется следующий синтаксис:

enum <имя перечисления> {
     // константы и другие элементы перечисления
}

Например, зададим через enum набор констант для разных типов геометрических фигур:

enum GeomType {
    POINT, LINE, TRIANGLE, RECTANGLE, ELLIPSE;
}

Здесь указанные элементы через запятую представляют собой константы перечисляемого типа с неявным набором модификаторов

public static final

Например, POINT – это публичная статическая константа типа GeomType. И мы легко можем в этом убедиться, выполнив в методе main следующую строчку:

System.out.println(GeomType.POINT.getClass());

В консоли увидим:

class com.javacourse.GeomType

Как же пользоваться этими константами? Предположим, у нас есть базовый класс Geom и дочерние классы Point и Line. Запишем их в виде:

abstract class Geom {
    public GeomType type;
}
 
class Point extends Geom {
    Point(GeomType type) {
        this.type = type;
    }
 
    void getPointMethod() {
        System.out.println("Класс Point");
    }
}
 
class Line extends Geom {
    Line(GeomType type) {
        this.type = type;
    }
 
    void getLineMethod() {
        System.out.println("Класс Line");
    }
}

Затем, в методе main создадим несколько экземпляров этих классов с указанием их типа:

public class Main {
    public static void main(String[] args) {
        final int N = 3;
        Geom g[] = new Geom[N];
 
        g[0] = new Line(GeomType.LINE); 
        g[1] = new Point(GeomType.POINT);
        g[2] = new Line(GeomType.LINE);
    }
}

И, далее, для приведения обобщенных ссылок g к соответствующим дочерним классам, мы можем прописать следующую проверку:

        for(Geom fig: g)
            switch (fig.type) {
                case POINT:
                    ((Point)fig).getPointMethod();
                    break;
                case LINE:
                    ((Line)fig).getLineMethod();
                    break;
            }

Причем, в условии оператора switch достаточно указать имя константы без имени самого перечисления. Виртуальная машина Java автоматически определит, что идет проверка на константу POINT (или LINE) перечисления GeomType, так как поле fig.type имеет такой тип.

Так как само по себе перечисление – это класс, то оно может содержать различные методы и константы. В частности, в любом перечислении имеется метод values(), который возвращает массив констант. Например, строчки:

        for(GeomType type: GeomType.values())
            System.out.println(type);

выведут в консоль список всех констант, определенных в перечислении GeomType.

Другой метод ordinal() возвращает порядковый номер константы:

        for(GeomType type: GeomType.values())
            System.out.println(type + " = " + type.ordinal());

С помощью метода equals() выполняется сравнение двух констант одного и того же перечисления:

        GeomType pt = GeomType.POINT;
        if(GeomType.POINT.equals(pt)) 
            System.out.println("Константы равны");

Или, можно использовать более общий метод compareTo(), который возвращает значения > 0, 0, < 0 как показано в следующем примере:

        if(GeomType.LINE.compareTo(pt) > 0)
            System.out.println("Константа LINE больше pt");
 
        GeomType line = GeomType.LINE;
        if(GeomType.POINT.compareTo(line) < 0)
            System.out.println("Константа POINT меньше line");
 
        if(GeomType.POINT.compareTo(pt) == 0)
            System.out.println("Константы POINT и pt равны");

Все эти примера показывают, что сами по себе константы в перечислении – это объекты, у которых есть свои методы. Но раз это объекты, то можно ли им указывать свои конструкторы и добавлять свои методы и поля? Оказывается да и делается это следующим образом:

enum GeomType {
    POINT("point"), LINE("line"), TRIANGLE("triangle");
 
    private String value;
 
    GeomType(String value) {
        this.value = value;
    }
 
    public String getValue() {
        return value;
    }
}

Смотрите, мы здесь добавили конструктор для перечисления GeomType и, так как константы – это объекты этого перечисления, то теперь, при их определении мы должны указать этот строковый параметр. Соответственно, строка для каждого объекта будет храниться в приватной переменной value, которую можно получить с помощью метода getValue():

public class Main {
    public static void main(String[] args) {
        final int N = 3;
        Geom g[] = new Geom[N];
 
        g[0] = new Line(GeomType.LINE);
        g[1] = new Point(GeomType.POINT);
        g[2] = new Line(GeomType.LINE);
 
        for(Geom fig: g) {
            String value = fig.type.getValue();
            System.out.println(value);
        }
    }
}

Обратите внимание, что конструктор перечисления всегда приватный, то есть, мы можем его использовать только внутри перечисления для создания объектов-констант. За пределами перечисления этот конструктор будет недоступен. Также следует знать, что при создании константы происходит автоматическое наследование от класса Enum – общего для всех перечислений. Соответственно, объекты-константы содержат все общедоступные методы перечислений.

Видео по теме