Вы уже знаете, что двумя фундаментальными строительными блоками Java-программ являются классы и интерфейсы. Tiger представляет еще один: перечисление. Обычно называемый просто словом enum, этот новый тип дает возможность представлять конкретные данные, которые принимают только определенный набор предопределенных во время присваивания значений.
Программисты с опытом уже конечно знают, что такую функциональность можно получить при помощи статических констант, как показано в листинге 1:
Листинг 1. Константы public static final
public class OldGrade {
public static final int A = 1;
public static final int B = 2;
public static final int C = 3;
public static final int D = 4;
public static final int F = 5;
public static final int INCOMPLETE = 6;
}
|
Примечание: Хотел бы выразить признательность O'Reilly Media, Inc., разрешившей использовать в этой статье пример кода из главы "Перечисления" моей книги Java 1.5 Tiger: Заметки разработчика (см. раздел "Ресурсы").
В дальнейшем вы можете настроить классы для работы с константами наподобие OldGrade.B, но при этом помните, что эти константы являются типом Java int, значит метод будет принимать любое значение int, даже если оно не попадает в список определенных в OldGrade значений. Следовательно, вам необходимо проверять нижнюю и верхнюю границу и, возможно, генерировать исключительную ситуацию IllegalArgumentException при появлении не правильного значения. Кроме того, если будет добавлено еще одно значение в этот список (например, OldGrade.WITHDREW_PASSING), вам будет нужно изменить верхнюю границу во всем коде для разрешения этого нового значения.
Другими словами, хотя использование классов с целочисленными константами подобным образом может быть приемлемым решением, оно не очень эффективно. К счастью, перечисления предлагают лучший путь.
В коде, приведенном в листинге 2, для обеспечения аналогичной листингу 1 функциональности используется перечисление:
Листинг 2. Простой перечислимый тип
package com.oreilly.tiger.ch03;
public enum Grade {
A, B, C, D, F, INCOMPLETE
};
|
Здесь я использовал новое ключевое слово enum, присвоил enum имя и указал разрешенные значения. Grade стал перечислимым типом, который вы можете использовать способом, показанном в листинге 3:
Листинг 3. Использование перечислимого типа
package com.oreilly.tiger.ch03;
public class Student {
private String firstName;
private String lastName;
private Grade grade;
public Student(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getFirstName() {
return firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return lastName;
}
public String getFullName() {
return new StringBuffer(firstName)
.append(" ")
.append(lastName)
.toString();
}
public void assignGrade(Grade grade) {
this.grade = grade;
}
public Grade getGrade() {
return grade;
}
}
|
Создав новое перечисление (grade) предварительно определенного типа, вы можете использовать его аналогично любой другой переменной экземпляра. Естественно, перечислению можно присвоить только одно из перечисленных значений (например, A, C, или INCOMPLETE). Обратите внимание также на то, что в assignGrade() нет кода проверки ошибок на выход за пределы границ.
Работа с перечислимыми значениями
Показанный вам пример является довольно простым, но перечислимые типы предлагают значительно больше. Перечислимые значения, которые можно использовать для итерации и в операторах switch, имеют большое значение.
Начнем с примера, показывающего, как пройти по значениям любого перечислимого типа. Эта техника, показанная в листинге 4, удобна для отладки, задач быстрой распечатки и загрузки значений enum в коллекцию (о чем я вскоре поговорю):
Листинг 4. Итерация по перечислимым значениям
public void listGradeValues(PrintStream out) throws IOException {
for (Grade g : Grade.values()) {
out.println("Allowed value: '" + g + "'");
}
}
|
После выполнения этого кода получим следующую информацию:
Листинг 5. Результат итерации
Allowed Value: 'A' Allowed Value: 'B' Allowed Value: 'C' Allowed Value: 'D' Allowed Value: 'F' Allowed Value: 'INCOMPLETE' |
Здесь происходит много вещей. Во-первых, я использую новый в Tiger цикл for/in (называемый также foreach или enhanced for). Кроме того, вы можете увидеть, что метод values() возвращает массив индивидуальных экземпляров Grade, каждый с одним из перечислимых значений. Другими словами, тип результата values() - Grade[].
Переключение с использованием enum
Проход по всем значениям enum является важной способностью, но еще более важной является способность принимать решение в зависимости от значения enum. Вы, конечно, могли бы написать множество операторов типа (grade.equals(Grade.A)), но это пустая трата времени. Tiger имеет удобную поддержку старого доброго оператора switch для enum. В листинге 6 показан пример использования этой возможности:
Листинг 6. Переключение с использованием enum
public void testSwitchStatement(PrintStream out) throws IOException {
StringBuffer outputText = new StringBuffer(student1.getFullName());
switch (student1.getGrade()) {
case A:
outputText.append(" excelled with a grade of A");
break;
case B: // fall through to C
case C:
outputText.append(" passed with a grade of ")
.append(student1.getGrade().toString());
break;
case D: // fall through to F
case F:
outputText.append(" failed with a grade of ")
.append(student1.getGrade().toString());
break;
case INCOMPLETE:
outputText.append(" did not complete the class.");
break;
}
out.println(outputText.toString());
}
|
Здесь перечислимое значение передается в оператор switch (вспомните, что getGrade() возвращает экземпляр Grade), и каждое выражение case имеет дело с конкретным значением. Это значение используется без префикса enum, означая, что вместо написания case Grade.A необходимо писать case A. Если вы этого не сделаете, компилятор не воспримет значение с префиксом.
Теперь вы должны понимать основы синтаксиса при использовании операторов switch, но есть еще некоторые вещи, которые необходимо знать.
Планирование наперед со switch
Как вы могли ожидать, с enum и switch можно использовать оператор default. В листинге 7 приведен пример такого использования:
Листинг 7. Добавление блока default
public void testSwitchStatement(PrintStream out) throws IOException {
StringBuffer outputText = new StringBuffer(student1.getFullName());
switch (student1.getGrade()) {
case A:
outputText.append(" excelled with a grade of A");
break;
case B: // fall through to C
case C:
outputText.append(" passed with a grade of ")
.append(student1.getGrade().toString());
break;
case D: // fall through to F
case F:
outputText.append(" failed with a grade of ")
.append(student1.getGrade().toString());
break;
case INCOMPLETE:
outputText.append(" did not complete the class.");
break;
default:
outputText.append(" has a grade of ")
.append(student1.getGrade().toString());
break;
}
out.println(outputText.toString());
}
|
Рассмотрим приведенный выше код и представим себе, что любое перечислимое значение, не обрабатываемое конкретно выражением case, обрабатывается выражением default. Это техника, которую вы всегда должны употреблять. И вот почему: предположим, что Grade enum был изменен другим программистом вашей группы (который, конечно же, забыл вам сказать об этом) в версию, приведенную в листинге 8:
Листинг 8. Добавление значений в Grade enum
package com.oreilly.tiger.ch03;
public enum Grade {
A, B, C, D, F, INCOMPLETE,
WITHDREW_PASSING, WITHDREW_FAILING
};
|
Теперь, если вы использовали бы эту новую версию Grade с кодом листинга 6, эти два новых значения были бы проигнорированы. Еще хуже - вы даже не увидели бы ошибки! В таких ситуациях использование выражения default очень важно. В листинге 7 эти значения, возможно, не обрабатываются элегантно, но в нем дается некоторое напоминание, что было добавлено новое значение и что вы должны обратить на него внимание. После этого вы будете иметь приложение, которое продолжает работать, не игнорирует значения и даже инструктирует вас о необходимости выполнить определенные действия. Это хороший стиль кодирования.
Те из вас, кто знаком с подходом к кодированию с использованием спецификаторов public static final, уже вероятно стали применять перечислимые значения как ключи в карте. Остальные, те кто не знает что это значит, взгляните на листинг 9, являющийся примером сообщений об ошибках общего типа, которые могут появиться при работе с файлами компоновки Ant:
Листинг 9. Коды состояния Ant
package com.oreilly.tiger.ch03;
public enum AntStatus {
INITIALIZING,
COMPILING,
COPYING,
JARRING,
ZIPPING,
DONE,
ERROR
}
|
Наличие читабельных сообщений об ошибках, присвоенных каждому коду состояния, позволило бы вам найти соответствующее сообщение и отправить его на консоль после того, как Ant выдаст один из кодов. Это отличный вариант использования в картах; каждый ключ в карте - это одно из этих перечислимых значений, а каждое значение – это сообщение об ошибке для этого ключа. Листинг 10 иллюстрирует, как это все работает:
Листинг 10. Карты enum
public void testEnumMap(PrintStream out) throws IOException {
// Создание карты с ключом и сообщением String
EnumMap<AntStatus, String> antMessages =
new EnumMap<AntStatus, String>(AntStatus.class);
// Инициализация карты
antMessages.put(AntStatus.INITIALIZING, "Initializing Ant...");
antMessages.put(AntStatus.COMPILING, "Compiling Java classes...");
antMessages.put(AntStatus.COPYING, "Copying files...");
antMessages.put(AntStatus.JARRING, "JARring up files...");
antMessages.put(AntStatus.ZIPPING, "ZIPping up files...");
antMessages.put(AntStatus.DONE, "Build complete.");
antMessages.put(AntStatus.ERROR, "Error occurred.");
// Итерация и распечатка сообщений
for (AntStatus status : AntStatus.values() ) {
out.println("For status " + status + ", message is: " +
antMessages.get(status));
}
}
|
В этом коде используется как оригинальные (см. раздел "Ресурсы"), так и новая структура EnumMap для создания новой карты. Перечислимый тип предоставляется через объект Class вместе с типом значений карты (в данном случае - простые строки). Результат работы этого метода приведен в листинге 11:
Листинг 11. Результат работы метода из листинга 10
[echo] Running AntStatusTester... [java] For status INITIALIZING, message is: Initializing Ant... [java] For status COMPILING, message is: Compiling Java classes... [java] For status COPYING, message is: Copying files... [java] For status JARRING, message is: JARring up files... [java] For status ZIPPING, message is: ZIPping up files... [java] For status DONE, message is: Build complete. [java] For status ERROR, message is: Error occurred. |
Перечислимые типы могут быть использованы совместно с наборами (set), и, аналогично новой структуре EnumMap, Tiger предоставляет новую реализацию Set EnumSet, позволяющую работать с побитными операторами. Кроме того, вы можете добавить методы в ваши перечислимые типы, использовать их для реализации интерфейсов и определять так называемые зависимые от значения тела классов, в которых конкретный код присваивается конкретному значению enum. Рассмотрение этих возможностей выходят за рамки данной статьи, но они хорошо документированы в других источниках (см. раздел "Ресурсы").
Используйте, но не злоупотребляйте
Одной из опасностей изучения новой версии любого языка является тенденция к сумасшествию при рассмотрении новых синтаксических структур. Неожиданно ваш код становится состоящим на 80% из базовых конструкций, аннотаций и перечислений. Так что используйте перечисления только там, где это имеет смысл. А где это имеет смысл? Как правило везде, где используются константы, например, в операторах switch. Если это значение одно (например, верхняя граница размера обуви, максимальное количество обезьян, которые могут поместиться в баррель), оставьте константу такой как она есть. Но если вы определяете набор значений, и любое из них может быть использовано для определенного типа данных, - прекрасно подходит перечисление...
- Оригинал статьи Getting started with enumerated types
- Загрузите Tiger и попробуйте поработать с ним самостоятельно.
- Официальная страница J2SE 5.0 является исчерпывающим ресурсом, который вы не должны пропустить.
- Специфика Tiger приведена в серии статей Джона Жуковски (John Zukowski) "Taming Tiger", в которой есть также краткие советы по дополнениям и изменениям в J2SE 5.0.
- Бретт Маклафлин (Brett McLaughlin) представил серию статей из двух частей по Tiger: Реферат по Tiger, часть 1: Добавление метаданных в Java-код; Реферат по Tiger 2: Аннотации пользователя.
- Статья Бретта Маклафлина (Brett McLaughlin) и Дэвида Флэнагана (David Flanagan) "Java 1.5 Tiger: Заметки разработчика". (O'Reilly & Associates; 2004) рассматривает в дружественном формате почти все новейшие функции (включая аннотации).
- Найдите сотни статей о практически каждом аспекте Java-программирования на сайте developerWorks в зоне технологии Java.
- Посетите Developer Bookstore для получения исчерпывающего списка технических книг, включая сотни статей, посвященных Java.

Бретт Маклафлин (Brett McLaughlin) работает с компьютерами со времен Logo (помните маленький треугольник?) с такими компаниями как, например, asNextel Communications и Lutris Technologies. В последние годы он стал одним из очень известных авторов и программистов в сообществе Java и XML. Его последняя книга "Java1.5 Tiger: Заметки разработчика", являющаяся первой доступной книгой по новой версии Java, и его классическая "Java и XML" остаются одними из определяющих работ по использованию XML-технологий в Java.