На сайте Quizful добавлен
Тест по Django.

Благодарим пользователя Averrin за составление теста.
Знаете ли Вы, что

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

Топ контрибуторов
loading
loading
Статистика

Тестов: 120, вопросов: 4327. Пройдено: 41781 / 130854.

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

head tail Информация о статье
категория
Java
дата15.05.2009
авторyohan
голосов30

Предыстория

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

Конструкция enum

Начнем с примера. Давайте опишем с помощью enum тип данных для хранения времени года:

enum Season { WINTER, SPRING, SUMMER, AUTUMN }

Ну и простой пример его использования:

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

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

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

Объявляя 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-класс наследует java.lang.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. Об этом, кстати, часто забывают. Почему-то многие твердо уверенны, что если функция принимает один аргумент и при некоторых услових выбрасывает IllegalArgumentException, то при передачи туда null, также будет неприменно выброшен IllegalArgumentException. Но это не относится к делу. Продолжим.

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

Иногда необходимо получить список всех элементов enum-класса во время выполнения. Для этих целей в каждом 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

С помощью 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 использование генериков в enum запрещено. Так следующий пример не скомпилируется:

enum Type<T> {}

Дальнейшее изучение

Для более глубокого понимания того, как работают перечисления в Java рекомендую ознакомиться с исходными кодами класса java.lang.Enum, а также воспользоваться декопмилятором Jad для изучения сгенерированного кода. Более того, изучение исходных кодов библиотеки Java абсолютно необходимо для понимания принципов работы многих механизмов в Java и полезно как эталон объектно-ориентированного дизайна.

--
Дмитрий Пекар, апрель 2009

Если Вам понравилась статья, проголосуйте за нее

Голосов: 30  loading...
admin   c0nst   lexxzar   clumsy   patisonka   breusov   giroy   doctorlife   alexis112   irene_irene   MaxT   fdcc   Dustty   matias   m0211spb   krolser   alniks   fedind   zheka82   javadev75   makk   generator   hatter   DodgeWP   kosolapiy   FAA   uniservise   ruel   bu_ma_ga   Thonatos  
Комментариев: 0
Добавить комментарий