Перечисление в Java

Перечисление (Enum) было введено в Java 1.5 как новый тип, чьи поля состоят из фиксированного набора констант. Например, мы можем создать направления в виде Java Enum с фиксированными полями, такими как EAST, WEST, NORTH и SOUTH.

Java Enum

В этом руководстве мы узнаем, как создавать перечисление (Enum). Мы также рассмотрим преимущества использования перечислений (enums) в Java и особенности типов перечислений. Мы также изучим использование Java Enum с методами valueOf, values, EnumSet и EnumMap на примерах.

Пример Java Enum

Ключевое слово enum в Java используется для создания типа перечисления. Давайте рассмотрим пример программы на языке Java с использованием перечисления (enum).

package com.journaldev.enums;

public enum ThreadStates {
	START,
	RUNNING,
	WAITING,
	DEAD;
}

В приведенном выше примере ThreadStates – это перечисление с фиксированными константами START, RUNNING, WAITING и DEAD.

Java Enum против Констант

Теперь давайте посмотрим, почему перечисление (enum) в Java лучше, чем обычные константы в классах Java. Давайте создадим аналогичный класс с константами в языке Java.

package com.journaldev.enums;

public class ThreadStatesConstant {
	public static final int START = 1;
	public static final int WAITING = 2;
	public static final int RUNNING = 3;
	public static final int DEAD = 4;
}

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

/**
* This method shows the benefit of using Enum over Constants
*/
private static void benefitsOfEnumOverConstants() {
	//Значения enum фиксированы
	simpleEnumExample(ThreadStates.START);
	simpleEnumExample(ThreadStates.WAITING);
	simpleEnumExample(ThreadStates.RUNNING);
	simpleEnumExample(ThreadStates.DEAD);
	simpleEnumExample(null);
		
	simpleConstantsExample(1);
	simpleConstantsExample(2);
	simpleConstantsExample(3);
	simpleConstantsExample(4);
	//Мы можем передать любую целочисленную константу
	simpleConstantsExample(5);
}

private static void simpleEnumExample(ThreadStates th) {
	if(th == ThreadStates.START) System.out.println("Thread started");
	else if (th == ThreadStates.WAITING) System.out.println("Thread is waiting");
	else if (th == ThreadStates.RUNNING) System.out.println("Thread is running");
	else System.out.println("Thread is dead");
}
	
private static void simpleConstantsExample(int i) {
	if(i == ThreadStatesConstant.START) System.out.println("Thread started");
	else if (i == ThreadStatesConstant.WAITING) System.out.println("Thread is waiting");
	else if (i == ThreadStatesConstant.RUNNING) System.out.println("Thread is running");
	else System.out.println("Thread is dead");
}

Если мы посмотрим на приведенный выше пример, мы имеем два риска при использовании констант, которые решаются с помощью enum.

  1. Мы можем передать любую целочисленную константу в метод simpleConstantsExample, но мы можем передать только фиксированные значения в simpleEnumExample, поэтому он обеспечивает безопасность типов.
  2. Мы можем изменить значение целочисленных констант в классе ThreadStatesConstant, но приведенная выше программа не будет выбрасывать никаких исключений. Наша программа может не работать, как ожидалось, но если мы изменяем константы enum, мы получим ошибку времени компиляции, которая исключает любую возможность проблем времени выполнения.

Методы перечисления Java

Теперь давайте рассмотрим еще несколько особенностей перечисления Java на примере.

package com.journaldev.enums;

import java.io.Closeable;
import java.io.IOException;

/**
 * This Enum example shows all the things we can do with Enum types
 *
 */
public enum ThreadStatesEnum implements Closeable{
	START(1){
		@Override
		public String toString(){
			return "START implementation. Priority="+getPriority();
		}

		@Override
		public String getDetail() {
			return "START";
		}
	},
	RUNNING(2){
		@Override
		public String getDetail() {
			return "RUNNING";
		}
	},
	WAITING(3){
		@Override
		public String getDetail() {
			return "WAITING";
		}
	},
	DEAD(4){
		@Override
		public String getDetail() {
			return "DEAD";
		}
	};
	
	private int priority;
	
	public abstract String getDetail();
	//Конструкторы перечисления всегда должны быть закрытыми.
	private ThreadStatesEnum(int i){
		priority = i;
	}
	
	//Перечисление может иметь методы
	public int getPriority(){
		return this.priority;
	}
	
	public void setPriority(int p){
		this.priority = p;
	}
	
	//Перечисление может переопределять функции
	@Override
	public String toString(){
		return "Default ThreadStatesConstructors implementation. Priority="+getPriority();
	}

	@Override
	public void close() throws IOException {
		System.out.println("Close of Enum");
	}
}

Важные моменты перечисления Java

Ниже приведены некоторые важные моменты для перечислений в Java.

  1. Все перечисления java неявно расширяют класс java.lang.Enum, который расширяет класс Object и реализует интерфейсы Serializable и Comparable. Поэтому мы не можем расширить перечисление никаким классом.
  2. Поскольку enum является ключевым словом, мы не можем заканчивать имя пакета этим словом, например, com.journaldev.enum не является допустимым именем пакета.
  3. Перечисление может реализовывать интерфейсы. Как в приведенном выше примере перечисления, оно реализует интерфейс Closeable.
  4. Конструкторы перечислений всегда являются закрытыми.
  5. Мы не можем создать экземпляр перечисления с помощью оператора new.
  6. Мы можем объявлять абстрактные методы в java перечислении, тогда все поля перечисления должны реализовать абстрактный метод. В приведенном выше примере getDetail() является абстрактным методом, и все поля перечисления реализовали его.
  7. Мы можем определить метод в перечислении, и поля перечисления также могут его переопределить. Например, метод toString() определен в перечислении, и поле перечисления START его переопределило.
  8. Поля java перечисления имеют пространство имен, мы можем использовать поле перечисления только с именем класса, как ThreadStates.START
  9. Перечисления могут использоваться в операторе switch, мы увидим это на практике в более поздней части этого урока.
  10. Мы можем расширить существующее перечисление, не нарушая при этом никакого существующего функционала. Например, мы можем добавить новое поле NEW в перечисление ThreadStates, не затрагивая существующий функционал.
  11. Поскольку поля перечисления являются константами, лучшая практика в Java – записывать их заглавными буквами и использовать подчеркивания для разделения слов. Например, EAST, WEST, EAST_DIRECTION и т. д.
  12. Константы перечисления неявно статичны и окончательны.
  13. Константы перечисления окончательны, но их переменные всё ещё можно изменять. Например, мы можем использовать метод setPriority() для изменения приоритета констант перечисления. Мы увидим это в примере ниже.
  14. Поскольку константы перечисления являются окончательными, мы можем безопасно сравнивать их, используя оператор “==” и методы equals(). Оба способа дадут одинаковый результат.

Java EnumSet, EnumMap, valueOf()

Теперь, когда мы знаем большую часть особенностей перечислений, давайте рассмотрим пример программы на Java с использованием перечислений. Затем мы узнаем ещё несколько особенностей перечисления.

package com.journaldev.enums;

import java.io.IOException;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Set;

public class JavaEnumExamples {

	public static void main(String[] args) throws IOException {
				
		usingEnumMethods();
		
		usingEnumValueOf();
		
		usingEnumValues();
		
		usingEnumInSwitch(ThreadStatesEnum.START);
		usingEnumInSwitch(ThreadStatesEnum.DEAD);
		
		usingEnumMap();
		
		usingEnumSet();
		
	}

	private static void usingEnumSet() {
		EnumSet enumSet = EnumSet.allOf(ThreadStatesEnum.class);
		for(ThreadStatesEnum tsenum : enumSet){
			System.out.println("Using EnumSet, priority = "+tsenum.getPriority());
		}
	}

	private static void usingEnumMap() {
		EnumMap<ThreadStatesEnum, String> enumMap = new EnumMap<ThreadStatesEnum,String>(ThreadStatesEnum.class);
		enumMap.put(ThreadStatesEnum.START, "Thread is started");
		enumMap.put(ThreadStatesEnum.RUNNING, "Thread is running");
		enumMap.put(ThreadStatesEnum.WAITING, "Thread is waiting");
		enumMap.put(ThreadStatesEnum.DEAD, "Thread is dead");
		
		Set keySet = enumMap.keySet();
		for(ThreadStatesEnum key : keySet){
			System.out.println("key="+key.toString()+":: value="+enumMap.get(key));
		}
		
	}

	private static void usingEnumInSwitch(ThreadStatesEnum th) {
		switch (th){
		case START:
			System.out.println("START thread");
			break;
		case WAITING:
			System.out.println("WAITING thread");
			break;
		case RUNNING:
			System.out.println("RUNNING thread");
			break;
		case DEAD:
			System.out.println("DEAD thread");
		}
	}

	private static void usingEnumValues() {
		ThreadStatesEnum[] thArray = ThreadStatesEnum.values();
		
		for(ThreadStatesEnum th : thArray){
			System.out.println(th.toString() + "::priority="+th.getPriority());
		}
	}

	private static void usingEnumValueOf() {
		ThreadStatesEnum th = Enum.valueOf(ThreadStatesEnum.class, "START");
		System.out.println("th priority="+th.getPriority());
	}

	private static void usingEnumMethods() throws IOException {
		ThreadStatesEnum thc = ThreadStatesEnum.DEAD;
		System.out.println("priority is:"+thc.getPriority());
		
		thc = ThreadStatesEnum.DEAD;
		System.out.println("Using overriden method."+thc.toString());
		
		thc = ThreadStatesEnum.START;
		System.out.println("Using overriden method."+thc.toString());
		thc.setPriority(10);
		System.out.println("Enum Constant variable changed priority value="+thc.getPriority());
		thc.close();
	}

}

Прежде чем объяснять другие важные особенности перечисления, давайте посмотрим вывод вышеприведенной программы.

priority is:4
Using overriden method.Default ThreadStatesConstructors implementation. Priority=4
Using overriden method.START implementation. Priority=1
Enum Constant variable changed priority value=10
Close of Enum
th priority=10
START implementation. Priority=10::priority=10
Default ThreadStatesConstructors implementation. Priority=2::priority=2
Default ThreadStatesConstructors implementation. Priority=3::priority=3
Default ThreadStatesConstructors implementation. Priority=4::priority=4
START thread
DEAD thread
key=START:: value=Thread is started
key=RUNNING:: value=Thread is running
key=WAITING:: value=Thread is waiting
key=DEAD:: value=Thread is dead
Using EnumSet, priority = 10
Using EnumSet, priority = 2
Using EnumSet, priority = 3
Using EnumSet, priority = 4

Важные моменты

  1. Метод usingEnumMethods() показывает, как создать объект перечисления и как мы можем использовать его методы. Он также показывает использование метода setPriority(int i) для изменения переменной перечисления.
  2. usingEnumValueOf() демонстрирует использование java.util.Enum valueOf(enumType, name), с помощью которого мы можем создать объект перечисления из строки. Он генерирует IllegalArgumentException, если указанный тип перечисления не имеет константы с указанным именем, или указанный объект класса не представляет собой тип перечисления. Он также генерирует NullPointerException, если какие-либо из аргументов равны null.
  3. Метод usingEnumValues() показывает использование метода values(), который возвращает массив, содержащий все значения перечисления в том порядке, в котором они объявлены. Обратите внимание, что этот метод автоматически генерируется компилятором Java для каждого перечисления. Вы не найдете реализации values() в классе java.util.Enum.
  4. Метод usingEnumInSwitch() показывает, как использовать константы перечисления в операторе switch case.
  5. Метод usingEnumMap() демонстрирует использование java.util.EnumMap, который введен в Java 1.5 Collections Framework. EnumMap – это реализация Map для использования с ключами типа перечисления. Все ключи в enum map должны быть из одного типа перечисления, который указывается явно или неявно при создании карты. Мы не можем использовать null в качестве ключа для EnumMap, и EnumMap не синхронизирован.
  6. Метод usingEnumSet() демонстрирует использование java.util.EnumSet, который является реализацией Set для использования с типами перечислений. Все элементы в наборе enum должны происходить из одного типа перечисления, который указывается явно или неявно при создании набора. EnumSet не синхронизирован, и null элементы не допускаются. Он также предоставляет некоторые полезные методы, такие как copyOf(Collection<E> c), of(E first, E... rest) и complementOf(EnumSet<E> s).

Вы можете ознакомиться со всеми примерами в нашем репозитории GitHub.

Ссылка: Документация Oracle

Source:
https://www.digitalocean.com/community/tutorials/java-enum