JavaのEnum

Java 1.5でEnumが導入され、そのフィールドは一定の定数のセットで構成される新しいタイプとなりました。例えば、Java Enumとして方向をEAST、WEST、NORTH、SOUTHという固定フィールドとして作成することができます。

Java Enum

このチュートリアルでは、Enumの作成方法を学びます。また、JavaでEnumを使用する利点やEnum型の特徴についても説明します。さらに、Java EnumのvalueOfvaluesEnumSetEnumMapの使用方法についても例を交えて学びます。

Java Enumの例

Java enumキーワードは、enum型を作成するために使用されます。以下は、Javaのenumの例プログラムです。

package com.journaldev.enums;

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

上記の例では、ThreadStatesはSTART、RUNNING、WAITING、DEADという固定の定数フィールドを持つenumです。

Java Enum vs 定数

次に、Javaの通常の定数フィールドよりもenumが優れている点を見てみましょう。まず、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;
}

今度は、Javaプログラムでenumと定数の両方がどのように使用されるかを見てみましょう:

/**
* 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);
	//任意のint定数を渡すことができます
	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");
}

上記の例を見ると、定数を使用することには2つのリスクがあり、それらはenumによって解決されます。

  1. 「simpleConstantsExample」メソッドには任意のint定数を渡すことができますが、「simpleEnumExample」には固定値のみを渡すことができるため、型の安全性が提供されます。
  2. 「ThreadStatesConstant」クラスのint定数の値を変更しても、上記のプログラムは例外をスローしません。プログラムは期待どおりに動作しないかもしれませんが、enumの定数を変更すると、実行時の問題の可能性を除去するためにコンパイル時エラーが発生します。

Java Enumメソッド

では、Javaのenumのさらなる機能を例を使って見てみましょう。

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();
	//Enumのコンストラクタは常にprivateである必要があります。
	private ThreadStatesEnum(int i){
		priority = i;
	}
	
	//Enumはメソッドを持つことができます。
	public int getPriority(){
		return this.priority;
	}
	
	public void setPriority(int p){
		this.priority = p;
	}
	
	//Enumは関数をオーバーライドすることができます。
	@Override
	public String toString(){
		return "Default ThreadStatesConstructors implementation. Priority="+getPriority();
	}

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

Java Enumの重要なポイント

以下は、JavaのEnumに関する重要なポイントです。

  1. すべてのJavaのenumは、暗黙的にjava.lang.Enumクラスを拡張します。このクラスはObjectクラスを拡張し、SerializableおよびComparableインターフェースを実装しています。そのため、enumでは他のクラスを拡張することはできません。
  2. enumはキーワードであるため、パッケージ名の末尾には使用できません。例えば、com.journaldev.enumは無効なパッケージ名です。
  3. enumはインターフェースを実装することができます。上記のenumの例では、Closeableインターフェースを実装しています。
  4. enumのコンストラクタは常にprivateです。
  5. new演算子を使用してenumのインスタンスを作成することはできません。
  6. Javaのenumでは、抽象メソッドを宣言することができます。その場合、すべてのenumフィールドは抽象メソッドを実装する必要があります。上記の例では、getDetail()が抽象メソッドであり、すべてのenumフィールドがそれを実装しています。
  7. enum内にメソッドを定義することもでき、enumフィールドはそれをオーバーライドすることもできます。たとえば、enum内でtoString()メソッドを定義し、enumフィールドSTARTがそれをオーバーライドしています。
  8. Javaのenumフィールドには名前空間があり、クラス名と組み合わせて使用する必要があります。例えば、ThreadStates.STARTのようにします。
  9. enumはswitch文で使用することができます。このチュートリアルの後半で具体的な使用例を見ていきます。
  10. 既存の列挙型を変更せずに拡張することができます。たとえば、ThreadStates列挙型に新しいフィールドNEWを追加することで、既存の機能に影響を与えることなく拡張できます。
  11. 列挙型のフィールドは定数であるため、Javaのベストプラクティスでは、ブロック文字とアンダースコアを使用して記述することが推奨されています。例えば、EAST、WEST、EAST_DIRECTIONなどです。
  12. 列挙型の定数は暗黙的にstaticおよびfinalです。
  13. 列挙型の定数はfinalですが、その変数は変更可能です。たとえば、setPriority()メソッドを使用して列挙型の定数の優先度を変更することができます。以下の例で使用方法を見てみましょう。
  14. 定数はfinalであるため、”==”演算子と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()メソッドは、enumオブジェクトを作成し、そのメソッドを使用する方法を示しています。また、enumの変数を変更するためにsetPriority(int i)メソッドの使用も示しています。
  2. usingEnumValueOf()では、java.util.EnumvalueOf(enumType, name)を使用してStringからenumオブジェクトを作成する方法を示しています。指定されたenumタイプに指定された名前の定数がない場合や、指定されたクラスオブジェクトがenumタイプを表していない場合は、IllegalArgumentExceptionがスローされます。また、引数のいずれかがnullの場合は、NullPointerExceptionがスローされます。
  3. usingEnumValues()メソッドは、enumの値を宣言された順序で含む配列を返すvalues()メソッドの使用方法を示しています。なお、このメソッドは、すべてのenumに対してJavaコンパイラによって自動生成されるものです。 java.util.Enumクラスでvalues()の実装を見つけることはできません。
  4. usingEnumInSwitch()メソッドは、enum定数をswitchケースで使用する方法を示しています。
  5. usingEnumMap()メソッドは、Java 1.5 Collections Frameworkで導入されたjava.util.EnumMapの使用方法を示しています。 EnumMapは、enum型のキーを使用するためのMap実装です。EnumMapのキーは、マップが作成されるときに明示的または暗黙に指定される単一のenum型から取得する必要があります。EnumMapではキーとしてnullを使用することはできず、EnumMapは同期されません。
  6. usingEnumSet()メソッドは、Enum型のセットを使用するためのSet実装であるjava.util.EnumSetの使用方法を示しています。Enumセットのすべての要素は、セットが作成される際に明示的または暗黙的に指定された単一の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