Перечисления — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Пример)
(Вся статья)
Строка 1: Строка 1:
==Предистория==
+
==Предыстория==
Программируя мы часто сталкиваемся с необходимостью ограничить множество допустимых значений для некоторого типа данных. Так, например, день недели может иметь 7 разных значений, месяц в году - 12, а время года - 4. Для решения подобных задач во многих языках программирования со статической типизацией предусмотрен специальный тип данных - перечисление <tex>(enum)</tex>.
+
Программируя, мы часто сталкиваемся с необходимостью ограничить множество допустимых значений для некоторого типа данных. Так, например, день недели может иметь 7 разных значений, месяц в году - 12, а время года - 4. Для решения подобных задач во многих языках программирования со статической типизацией предусмотрен специальный тип данных - перечисление <tex>(enum)</tex>.  
 +
Данный тип данных появился в Java начиная с версии 1.5. (enum) предоставляет множество удобств как в компактности записи, так и в удобстве использования. Так же, по сравнению, допустим, с C++, перечисления в Java представляют собой полноценные объекты, что предоставляет разработчику гораздо большую гибкость.
 +
 
 
==Введение ==
 
==Введение ==
В <tex>Java</tex>, начиная с версии 1.5, помимо всего прочего появились так называемые перечисления (enum). Существует целый ряд плюсов от использования перечислений против именованных констант:
+
Перечисления наследуются от класса <tex>java.lang.Enum</tex>, у которого есть ряд удобных методов. Вот одни из них :
Компилятор гарантирует корректную проверку типов
 
Удобство итерации по всем возможным значениям перечисления
 
Они занимают меньше места в <tex>switch-</tex>блоке (не нужно указывать имя класса)
 
и т.д.
 
 
 
Однако по сравнению, допустим, с C++, перечисления в Java представляют собой полноценные объекты, что предоставляет разработчику гораздо большую гибкость.
 
Во-первых, все перечисления наследуются от класса <tex>java.lang.Enum</tex>, у которого есть ряд удобных методов, а именно:
 
  
 
<tex>— name()</tex> — имя константы в виде строки
 
<tex>— name()</tex> — имя константы в виде строки
Строка 17: Строка 12:
 
<tex>— valueOf()</tex> — статический метод, позволяющий получить объект перечисления по классу и имени
 
<tex>— valueOf()</tex> — статический метод, позволяющий получить объект перечисления по классу и имени
  
Далее, как уже было озвучено, у класса перечисления есть возможность получить все возможные значения перечисления путем вызова метода <tex>java.lang.Class.getEnumConstants()</tex> у класса перечисления
+
Так же, у класса перечисления есть возможность получить все возможные значения перечисления путем вызова метода <tex>java.lang.Class.getEnumConstants()</tex>.
В классе перечисления имеется возможность задавать конструкторы (только приватные), поля и методы
+
В классе перечисления имеется возможность задавать конструкторы (только приватные), поля и методы.
Перечисления могут реализовывать любые интерфейсы
+
 
При этом методы в перечислении могут быть абстрактными, а конкретные экземпляры констант могут определять такие методы (как, впрочем, и переопределять уже определенные)
+
== Перечисление - это класс ==
 +
Объявляя enum, мы неявно создаем класс производный от java.lang.Enum. Условно конструкция enum Mark { ... } эквивалентна
 +
 
 +
class Mark extends java.lang.Enum { ... }.
 +
 
 +
И хотя явным образом наследоваться от <tex>java.lang.Enum</tex> нам не позволяет компилятор, все же в том, что <tex>enum</tex> наследуется, легко убедиться с помощью <tex>reflection:</tex>
 +
 
 +
System.out.println(Mark.class.getSuperclass());
 +
 
 +
На консоль будет выведено:
 +
 
 +
class java.lang.Enum
 +
Собственно наследование за нас автоматически выполняет компилятор Java.
 +
 
  
 
==Конструкция enum==
 
==Конструкция enum==
Начнем с примера. Давайте опишем с помощью enum тип данных для хранения времени года:
+
Начнем с примера. Давайте опишем с помощью enum тип данных для хранения оценки студента:
 +
 
 +
enum Mark { A, B, C, D, E, F }
  
enum Season { WINTER, SPRING, SUMMER, AUTUMN }
+
Здесь мы использовали новое ключевое слово 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;
 +
  }
 +
}
  
Season season = Season.SPRING;
+
Создав новое перечисление (Mark) предварительно определенного типа, вы можете использовать его аналогично любой другой переменной экземпляра. Естественно, перечислению можно присвоить только одно из перечисленных значений (например, A, C, или F). Обратите внимание также на то, что в getMark() нет кода проверки ошибок на выход за пределы границ.
if (season == Season.SPRING) season = Season.SUMMER;
 
System.out.println(season);
 
  
В результате выполнения которого на консоль будет выведено SUMMER.
+
==Использование enum==
== Перечисление - это класс ==
+
 
Объявляя enum мы неявно создаем класс производный от java.lang.Enum. Условно конструкция enum Season { ... } эквивалентна
+
===Итерация по 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===
 +
Рассмотренные ранее примеры являются довольно простыми, но перечислимые типы предлагают значительно больше. Перечислимые значения, которые можно использовать для итерации и в операторах <tex>switch</tex>, имеют большое значение.
 +
 
 +
Для сравнения значения mark с разными возможными оценками можно было использовать следующую запись:
 +
 
 +
mark.equals(Mark.A)
 +
 
 +
Но множество сравнений заняло бы много места.
 +
Для этого мы используем <tex>switch</tex>, в результате чего у нас получится:
 +
 
 +
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;
 +
}
 +
 
 +
В результате чего он выведет соответствующую оценку. Так же можно употребить <tex>default </tex>, на тот случай, если вдруг другой программист добавил новые значения в Mark, а вас не предупредил. Поставив для варианта <tex>default </tex> вывод сообщения об отсутствии в списке значенияя mark, вы обнаружите это изменение.
 +
 
 +
==Получение елемента enum по строковому представлению его имени==
 +
Довольно часто возникает задача получить элемент enum по его строковому представлению. Для этих целей в каждом enum-классе компилятор автоматически создает специальный статический метод: public static EnumClass valueOf(String name), который возвращает элемент перечисления EnumClass с названием, равным name. Пример использования:
 +
String name = "B";
 +
Mark mark = Mark.valueOf(name);
  
class Season extends java.lang.Enum { ... }.  
+
В результате чего в mark будет записано значение B.
  
И хотя явным образом наследоваться от <tex>java.lang.Enum</tex> нам не позволяет компилятор, все же в том, что <tex>enum</tex> наследуется, легко убедиться с помощью <tex>reflection:</tex>
+
==Добавление методов в enum==
 +
У Вас есть возможность добавлять собственные методы как в enum-класс, так и в его элементы:
 +
enum MarkBool {
 +
  GOOD, BAD;
 +
 
 +
  public MarkBool opposite() { return this == GOOD ? BAD : GOOD; }
 +
}
 +
 
 +
==Наследование в enum==
 +
С помощью enum в Java можно реализовать иерархию классов, объекты которой создаются в единственном экземпляре и доступны статически. При этом элементы enum могут содержать собственные конструкторы.
  
  System.out.println(Season.class.getSuperclass());
+
  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
  
class java.lang.Enum
+
==Пример==
Собственно наследование за нас автоматически выполняет компилятор Java.
+
Раньше класс бинарных операций мы записывали следующим образом:
  
== Пример ==
 
Раньше класс бинарные операции мы делали вот так
 
 
  public class BinaryOperation {
 
  public class BinaryOperation {
 
   public class Plus  {
 
   public class Plus  {
Строка 72: Строка 186:
 
   }
 
   }
 
  }
 
  }
Проблема была в том, что пригодилось делать много проверок извне для вызова этих функции. Можно сделать гораздо проще и удобнее
+
 
 +
С использованием enum все значительно упрощается:
 +
 
 
   public enum BinaryOperation {
 
   public enum BinaryOperation {
 
   Plus("+")  {
 
   Plus("+")  {

Версия 15:49, 29 сентября 2013

Предыстория

Программируя, мы часто сталкиваемся с необходимостью ограничить множество допустимых значений для некоторого типа данных. Так, например, день недели может иметь 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);
}