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

При программировании часто необходимо использовать ограниченное множество допустимых значений для некоторого типа данных. Так, например, день недели может иметь 7 разных значений, месяцев в году не более 12, и всего 4 времени года. Для решения подобных задач во многих языках программирования предусмотрен специальный тип данных - перечисление (enum). В Java перечисление enum появилось не сразу только в версии Java 5.

Синтаксис перечисления enum

Описание с помощью enum типа данных Season для хранения времени года можно представить в следующем виде :

enum Season { WINTER, SPRING, SUMMER, AUTUMN }

Простой пример использования enum :

 Season season = Season.WINTER;
 if (season == Season.WINTER) 
     season =  Season.SPRING;
 System.out.println(season.name());

В результате выполнения данного кода в консоль будет выведен текст SPRING.

Перечисление enum - это класс

При объявлении переменной типа enum неявно создается класс производный от java.lang.Enum. Условно рассматриваемая конструкция enum Season { ... } эквивалентна class Season extends java.lang.Enum { ... }. Явным образом наследоваться от java.lang.Enum не позволяет компилятор. Но то, что enum наследуется, легко убедиться с помощью reflection:

 System.out.println(Season.class.getSuperclass());

В консоль будет выведено :


class java.lang.Enum
 

Таким образом наследование за разработчика автоматически выполняет компилятор Java. Далее в тексте enum-классом будет называться класс, созданный компилятором для реализации перечисления, а возможные значения перечисляемого типа - элементами enum'a.

Элементы перечисления enum

Элементы enum Season (WINTER, SPRING и т.д.) - это статически доступные экземпляры enum-класса Season. Их статическая доступность позволяет выполнять сравнение с помощью оператора сравнения ссылок ==. Например :

Season season = Season.SUMMER;
if (season == Season.AUTUMN) 
    season = Season.WINTER;

Название и порядковый номер элемента enum

Пример использования методов enum для извлечения информации об объекте :

Season season = Season.WINTER;
System.out.println("season.name() = " + season.name() + ", season.toString() = " + season.toString() + 
                   ", season.ordinal() = " + season.ordinal());

В консоле будет представлено:


season.name() = WINTER, season.toString() = WINTER, season.ordinal() = 0
 

В примере использованы методы name(), toString() и ordinal(). Семантика методов - очевидна. Следует обратить внимание, что данные методы enum-класс наследует от класса java.lang.Enum

Получение елемента enum по строковому представлению его имени

Довольно часто возникает задача необходимости получения элемента enum по его строковому представлению. Для этих целей в каждом enum-классе компилятор автоматически создает специальный статический метод : public static EnumClass valueOf (String name), который возвращает элемент перечисления EnumClass с названием, равным name. Например:

String name = "WINTER";
Season season = Season.valueOf(name);

В результате выполнения кода переменная season будет равна Season.WINTER.
Cледует обратить внимание, что если элемент не будет найден, то будет вызвано исключение IllegalArgumentException, а в случае, если name=null - NullPointerException. Об этом не следует забывать.

Получение элементов перечисления

Если необходимо получить список всех элементов enum-класса во время выполнения, то для этих целей в следует использовать метод: public static EnumClass[] values(). Например:

System.out.println(Arrays.toString(Season.values()));

В консоль будет выведено:


[WINTER, SPRING, SUMMER, AUTUMN]
 

Необходимо иметь в виду, что ни метод valueOf(), ни метод values() не определены в классе java.lang.Enum. Они автоматически добавляются на этапе компиляции enum-класса.

Добавление методов в enum-класс

Можно добавлять собственные методы как в enum-класс, так и в его элементы:

enum Direction {
   UP, DOWN;
   public Direction opposite() { return this == UP ? DOWN : UP; }
}

Тот же пример, но с полиморфизмом:

 enum Direction {
   UP {
        public Direction opposite() { return DOWN; }
   },
   DOWN {
        public Direction opposite() { return UP; }
   };
   public abstract Direction opposite();
}

Наследование в enum

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

enum Type {
    INT(true) {
        public Object parse(String string) {
            return Integer.valueOf(string);
        }
    },
    INTEGER(false) {
        public Object parse(String string) {
            return Integer.valueOf(string);
        }
    },
    STRING(false) {
        public Object parse(String string) {
            return string;
        }
    };

    boolean primitive;
    Type(boolean primitive) {
        this.primitive = primitive;
    }

    public boolean isPrimitive() {
        return primitive; 
    }
    public abstract Object parse(String string);
}

Здесь объявляется перечисление Type с тремя элементами INT, INTEGER и STRING. Компилятор создаст следующие классы и объекты:

  • Type - класс производный от java.lang.Enum
  • INT - объект 1-го класса производного от Type
  • INTEGER - объект 2-го класса производного от Type
  • STRING - объект 3-го класса производного от Type

Три производных класса будут созданы с полиморфным методом Object parse (String) и конструктором Type (..., boolean). При этом объекты классов INT, INTEGER и STRING существуют в единственном экземпляре и доступны статически. В этом можно убедиться, выполнив следующий код :

 System.out.println(Type.class);
 System.out.println(Type.INT    .getClass() + " " + Type.INT    .getClass().getSuperclass());
 System.out.println(Type.INTEGER.getClass() + " " + Type.INTEGER.getClass().getSuperclass());
 System.out.println(Type.STRING .getClass() + " " + Type.STRING .getClass().getSuperclass());

Результат выполнения кода :


class Type
class Type$1 class Type
class Type$2 class Type
class Type$3 class Type
 

Таким образом, компилятор создал класс Type и 3 nested класса, производных от Type.

Декомпилированный enum-class с наследованием

В подтверждение вышесказанному можно привести результат декомпиляции перечисления Type из примера выше:

abstract class Type extends Enum {
    public static Type[] values() {
        return (Type[]) $VALUES.clone();
    }

    public static Type valueOf(String name) {
        return (Type) Enum.valueOf(t / T$Type, name);
    }

    public boolean isPrimitive() {
        return primitive;
    }

    public abstract Object parse(String s);

    public static final Type INT;
    public static final Type INTEGER;
    public static final Type STRING;
    boolean primitive;
    private static final Type $VALUES[];

    static {
        INT = new Type("INT", 0, true) {
            public Object parse(String string) { return Integer.valueOf(string); }
        };
        INTEGER = new Type("INTEGER", 1, false) {
            public Object parse(String string) { return Integer.valueOf(string); }
        };
        STRING = new Type("STRING", 2, false) {
            public Object parse(String string) { return string; }
        };

        $VALUES = (new Type[]{
                INT, INTEGER, STRING
        });
    }

    private Type(String s, int i, boolean primitive) {
        super(s, i);
        this.primitive = primitive;
    }
}

Перечисления и параметрический полиморфизм

Может возникнуть вопрос: "А почему вышеуказанное перечисление Type не использует generics" ? Причина кроется в том, что в Java использование generics в enum запрещено. Так следующий пример не будет скомпилирован:

enum Type<T> {}
Наверх
  Рейтинг@Mail.ru