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

Java

Перечисления (Enum) были введены в Java начиная с версии 1.5 с целью улучшения контроля за типобезопасностью (невозможно назначить переменной значение извне заданного множества допустимых значений). До этого для реализации перечислений предлагалось использовать классы со статическими константами.

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

public class EnumExample_01 {
	enum Cardsuit { CLUBS, DIAMONDS, SPADES, HEARTS }
	
	public static void main(String[] args) {
		Cardsuit cs = Cardsuit.HEARTS;
		if(cs == Cardsuit.HEARTS)
			System.out.println("Черви");
	}
}

В результате на консоль будет выведено Черви . Стоит отметить, что перечисления не нужно сравнивать через equals, достаточно сравнения по ссылке.

Одной из наиболее ценной возможностью применения перечислений в Java является использование его внутри оператора switch:

Cardsuit cs = Cardsuit.CLUBS;
	switch(cs){ 
		case CLUBS: System.out.println("BLACK"); break; 
		case DIAMONDS: System.out.println("RED"); break;
		case SPADES: System.out.println("BLACK"); break;
		case HEARTS: System.out.println("RED"); break;
	}

В Java, в отличие от большинства других языков программирования, перечисления представляют собой полноценный класс, наследуемый от java.lang.Enum (наследование автоматически выполняет компилятор Java), в который можно добавлять произвольное количество конструкторов, полей и методов.

java.lang.Enum содержит ряд полезных методов:

  • public final String name() – возвращает задекларированное имя константы.
  • public String toString() – по умолчанию возвращает задекларированное имя константы, однако в отличие от name() может быть переопределен.
  • public final int ordinal() – порядковый номер в котором экземпляр перечисления обозначен внутри enum.
  • public final int compareTo(E o) — имитирует порядок, предоставляемый методом ordinal(). Т.О. enum ограничивает сравнения только порядком их объявления.

Так же есть два метода не определенные в классе java.lang.Enum, а добавляемые в процессе компиляции:

  • public static EnumClass valueOf(String name) – возвращает элемент перечисления EnumClass с названием, равным name. В случае если элемент не будет найден, будет выброшен IllegalArgumentException или NullPointerException если аргумент будет null.
  • public static EnumClass[] values() – возвращает список всех элементов enum-класса.

Важным моментом является факт того, что невозможно создавать экземпляры перечисления вне границ Enum, поскольку у него нет public конструктора, и компилятор не допускает наличие public конструкторов внутри Enum. Экземпляры должны создаваться внутри самого Enum.

Примечание: экземпляр перечисления создается в момент первого обращения к ней.

Пример, охватывающий все вышесказанное:

public class EnumExample_02 {
	public enum Length {
		KM("Километр", 1000), M("Метр", 1), DM("Дециметр", 0.1), CM("Сантиметр", 0.01), MM("Миллиметр",0.001);
		
		String name;
		double d;
		
		private Length(String name, double d) {
			this.name = name;
			this.d = d;
		}

		@Override
		public String toString() {
			return name;
		}
		
		double coefficient(Length inp) {
			return d/inp.d;
		}
		
	}
	public static void main(String[] args) {
		for(Length l1: Length.values())
			for(Length l2: Length.values())
				if(l1!=l2)
					System.out.println("1 " + l1 + " = " + l1.coefficient(l2) + " " + l2);

		//Два способа получения enum объекта по строковому представлению
		System.out.println(Length.valueOf("KM"));
		System.out.println(Enum.valueOf(Length.class, "KM"));
	}
}

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

1 Километр = 1000.0 Метр
1 Километр = 10000.0 Дециметр
1 Километр = 100000.0 Сантиметр
1 Километр = 1000000.0 Миллиметр
1 Метр = 0.001 Километр
1 Метр = 10.0 Дециметр
1 Метр = 100.0 Сантиметр
1 Метр = 1000.0 Миллиметр
1 Дециметр = 1.0E-4 Километр
1 Дециметр = 0.1 Метр
1 Дециметр = 10.0 Сантиметр
1 Дециметр = 100.0 Миллиметр
1 Сантиметр = 1.0E-5 Километр
1 Сантиметр = 0.01 Метр
1 Сантиметр = 0.09999999999999999 Дециметр
1 Сантиметр = 10.0 Миллиметр
1 Миллиметр = 1.0E-6 Километр
1 Миллиметр = 0.001 Метр
1 Миллиметр = 0.01 Дециметр
1 Миллиметр = 0.1 Сантиметр
Километр
Километр

Наследование и полиморфизм в enum

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

Например, можно реализовать набор оконных функций для преобразования Фурье (из реального проекта):

public enum WIN{
	NONE{
		public double coef(int n, int N){return 1;};
	}, WELCH {
		public double coef(int n, int N){return 1 - Math.pow(2.*(n - 0.5*(N - 1))/(N + 1),2);};
	}, TRIANGULAR {
		public double coef(int n, int N){return 2.*(0.5*N - Math.abs(n - 0.5*N))/N;};
	}, BARTLETT {
		public double coef(int n, int N){return 2.*(0.5*(N - 1) - Math.abs(n - 0.5*(N - 1)))/(N - 1);};
	}, HANNING {
		public double coef(int n, int N){return 0.5*(1 - Math.cos(2*n*Math.PI/(N - 1)));};
	}, HAMMING {
		public double coef(int n, int N){return 0.54 - 0.46*Math.cos(2.*Math.PI*n/(N - 1));};
	}, BLACKMAN {
		public double coef(int n, int N){return 0.42 - 0.5*Math.cos(2.*Math.PI*n/(N - 1)) + 0.08*Math.cos(4.*Math.PI*n/(N - 1));};
	};
	
	/**
	 * Множитель для данных.
	 * @param n номер точки для расчета множителя.
	 * @param N полное число отсчетов.
	 */
	public abstract double coef(int n, int N);
	
	/**
	 * Расчет множителя для спектральной плотности
	 * @param N
	 */
	public double calc_PDMF(int N){
		double sum = 0;
		for(int n = 0; n < N; n++)
			sum += Math.pow(coef(n, N), 2);
		return N/sum;
	}
}

Enum в java может реализовывать интефейс, что собственно он уже и делает по умолчанию, реализуя интерфейс Comparable<E>. И именно поэтому Enum по умолчанию может использоваться в упорядоченных коллекциях (например, TreeSet или TreeMap).

Наследовать класс Enum не может, т.к. уже наследуется от java.lang.Enum, а множественного наследования классов в Java нет.

В дополнение стоит указать на наличие в Java Collections API специальных классов EnumSet и EnumMap которые производительнее чем HashSet и HashMap при использовании enum в качестве ключей. По причине того, что если enum содержит не более 64 различных значений, то EnumSet хранит всё в одном поле типа long в битовой маске. А EnumMap хранит все значения в обычном массиве той же длины, сколько элементов в enum (при этом его длинна никогда не изменяется), а ключи не хранит вовсе. Так как у каждого значения в enum есть порядковый номер, получаемый при помощи ordinal().

Михаил Миронов

Живу в Нижнем Новгороде, работаю программистом с 2017 года, основная специализация Java, но также хорошо знаю PHP, Python, XML, HTML/CSS.

Добавить комментарий