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

Материал из Викиконспекты
Перейти к: навигация, поиск

Предыстория

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

Введение

Перечисления наследуются от класса [math]java.lang.Enum[/math], у которого есть ряд удобных методов. Вот одни из них :

[math]— name()[/math] — имя константы в виде строки

[math]— ordinal()[/math] — порядок константы (соответствует порядку, в котором объявлены константы)

[math]— valueOf()[/math] — статический метод, позволяющий получить объект перечисления по классу и имени

Так же, у класса перечисления есть возможность получить все возможные значения перечисления путем вызова метода [math]java.lang.Class.getEnumConstants()[/math]. В классе перечисления имеется возможность задавать конструкторы (только приватные), поля и методы.

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

Объявляя enum, мы неявно создаем класс производный от java.lang.Enum. Условно конструкция enum Mark { ... } эквивалентна

class Mark extends java.lang.Enum { ... }. 

И хотя явным образом наследоваться от [math]java.lang.Enum[/math] нам не позволяет компилятор, все же в том, что [math]enum[/math] наследуется, легко убедиться с помощью [math]reflection:[/math]

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

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

class java.lang.Enum

Собственно наследование за нас автоматически выполняет компилятор Java.


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

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

enum Mark { A, B, C, D, E, F }

Здесь мы использовали новое ключевое слово enum, присвоили ему имя и указал разрешенные значения. Grade стал перечислимым типом, который вы можете использовать, например, следующим способом:

public class Student {
 private String Name;
 private Mark mark;
 public Student(String Name, Mark mark) {
   this.Name = Name;
   this.mark = mark;
 }
 public void setName(String Name) {
   this.Name = Name;
 }
 public String getName() {
   return Name;
 }
 public void setMark(Mark mark) {
   this.mark = mark;
 }
 public Mark getMark() {
   return mark;
 }
}

Создав новое перечисление (Mark) предварительно определенного типа, вы можете использовать его аналогично любой другой переменной экземпляра. Естественно, перечислению можно присвоить только одно из перечисленных значений (например, A, C, или F). Обратите внимание также на то, что в getMark() нет кода проверки ошибок на выход за пределы границ.

Использование enum

Итерация по enum

Рассмотрим пример, показывающий, как пройти по значениям любого перечислимого типа.

public void PrintAllMarks(PrintStream out) throws IOException {
 for (Mark mark : Mark.values()) {
   out.println("Allowed marks : " + mark);
 }
}

В результате чего мы получим следующий текст:

Allowed marks : A
Allowed marks : B
Allowed marks : C
Allowed marks : D
Allowed marks : E
Allowed marks : F

Так же можно использовать следующую запись для вывода значений на экран:

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

В результате будет выведено:

[A, B, C, D, E, F]

Переключение с enum

Рассмотренные ранее примеры являются довольно простыми, но перечислимые типы предлагают значительно больше. Перечислимые значения, которые можно использовать для итерации и в операторах [math]switch[/math], имеют большое значение.

Для сравнения значения mark с разными возможными оценками можно было использовать следующую запись:

mark.equals(Mark.A)

Но множество сравнений заняло бы много места. Для этого мы используем [math]switch[/math], в результате чего у нас получится:

switch (mark) {
   case A: 
     outputText.append("Great Mark A");
     break;   
   case B: // fall through to C
   case C: 
     outputText.append ("Good Mark ")
               .append(mark.toString());
     break;
   case D: // fall through to E
   case E:
     outputText.append("Bad Mark ")
               .append(student1.getGrade().toString());
     break;
   case F:
     outputText.append("Negative Mark F");
     break;
}

В результате чего он выведет соответствующую оценку. Так же можно употребить [math]default [/math], на тот случай, если вдруг другой программист добавил новые значения в Mark, а вас не предупредил. Поставив для варианта [math]default [/math] вывод сообщения об отсутствии в списке значенияя mark, вы обнаружите это изменение.

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

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

String name = "B"; 
Mark mark = Mark.valueOf(name); 

В результате чего в mark будет записано значение B.

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

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

enum MarkBool { 
  GOOD, BAD; 
  
  public MarkBool opposite() { return this == GOOD ? BAD : GOOD; }
}

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

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

enum MarkBool { 
  GOOD { 
       public MarkBool opposite() { return BAD; } 
  }, 
  BAD { 
       public MarkBool opposite() { return GOOD; } 
  }; 

  public abstract MarkBool opposite(); 
}

Здесь объявляется перечисление MarkBool с двумя элементами BAD и GOOD. Компилятор создаст следующие классы и объекты:

•MarkBool - класс производный от java.lang.Enum
•BAD - объект 1-го класса производного от MarkBool
•GOOD - объект 2-го класса производного от MarkBool

Два производных класса будут созданы с полиморфным методом Object parse(String) и конструктором MarkBool(..., boolean) При этом объекты классов BAD и GOOD существуют в единственном экземпляре и доступны статически. В этом можно убедится:

System.out.println(MarkBool.class); 
System.out.println(MarkBool.BAD.getClass() + " " + MarkBool.BAD.getClass().getSuperclass()); 
System.out.println(MarkBool.GOOD.getClass() + " " + MarkBool.GOOD.getClass().getSuperclass());

Результат будет следующим:

class MarkBool
class MarkBool $1 class MarkBool
class MarkBool $2 class MarkBool

Пример

Раньше класс бинарных операций мы записывали следующим образом:

public class BinaryOperation {
  public class Plus  {
   public int calculate (int a, int b){
     return a + b;
   }
  }
  public class Minus  {
    public int calculate (int a, int b){
      return a - b;
    }
  }
   public class Division  {
     public int calculate (int a, int b){
       return a / b;
     }
   }
   public class Times  {
      public int calculate (int a, int b){
        return a * b;
      }
  }
}

С использованием enum все значительно упрощается:

 public enum BinaryOperation {
  Plus("+")  {
    public int calculate(int a, int b){
       return a + b;
    }
  }
   Minus("-")  {
    public int calculate(int a, int b){
       return a - b;
    }
  }
  Division("/")  {
    public int calculate(int a, int b){
       return a / b;
    }
  }
   Times("*")  {
    public int calculate(int a, int b){
       return a * b;
    }
  }
}
abstract public int calculate(int a, int b);
}