Java 中的枚举

枚举在Java 1.5中作为一种新类型引入,其字段由一组固定的常量组成。例如,我们可以将方向创建为Java枚举,其固定字段为EAST、WEST、NORTH和SOUTH。

Java枚举

在本教程中,我们将学习如何创建枚举。我们还将探讨在Java中使用枚举的好处以及枚举类型的特性。我们还将学习如何使用Java枚举valueOf、枚举valuesEnumSetEnumMap,并附有示例。

Java枚举示例

Java enum关键字用于创建枚举类型。让我们来看一个Java枚举示例程序。

package com.journaldev.enums;

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

在上面的示例中,ThreadStates是一个具有固定常量字段START、RUNNING、WAITING和DEAD的枚举。

Java枚举 vs 常量

现在让我们看看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;
}

现在让我们看看在Java程序中如何使用枚举和常量:

/**
* This method shows the benefit of using Enum over Constants
*/
private static void benefitsOfEnumOverConstants() {
	// 枚举值是固定的
	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");
}

如果我们看上面的例子,使用常量存在两个风险,而枚举可以解决这些问题。

  1. 我们可以将任何 int 常量传递给 simpleConstantsExample 方法,但是我们只能将固定值传递给 simpleEnumExample,因此它提供了类型安全性。
  2. 我们可以更改 ThreadStatesConstant 类中的 int 常量值,但是上述程序不会抛出任何异常。我们的程序可能无法按预期工作,但是如果更改枚举常量,则会收到编译时错误,从而消除了任何运行时问题的可能性。

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类并实现了SerializableComparable接口。因此我们不能在枚举中扩展任何类。
  2. 由于enum是一个关键字,我们不能以它结束包名,例如com.journaldev.enum不是一个有效的包名。
  3. 枚举可以实现接口。就像上面的枚举示例中,它实现了Closeable接口。
  4. 枚举的构造函数始终是私有的。
  5. 我们不能使用new运算符创建枚举的实例。
  6. 我们可以在Java枚举中声明抽象方法,然后所有的枚举字段都必须实现该抽象方法。在上面的示例中,getDetail()是抽象方法,所有的枚举字段都实现了它。
  7. 我们可以在枚举中定义方法,枚举字段也可以重写它们。例如,toString()方法在枚举中被定义,枚举字段START对其进行了重写。
  8. Java枚举字段有命名空间,我们只能使用类名加上枚举字段的方式来使用它,例如ThreadStates.START
  9. 枚举可以在switch语句中使用,在本教程的后面部分我们将看到它的实际应用。
  10. 我们可以在不影响任何现有功能的情况下扩展现有的枚举。例如,我们可以在ThreadStates枚举中添加一个新字段NEW,而不影响任何现有功能。
  11. 由于枚举字段是常量,Java最佳实践是使用大写字母和下划线表示它们的名称。例如EAST、WEST、EAST_DIRECTION等。
  12. 枚举常量隐式地是静态和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()方法展示了如何创建一个枚举对象以及如何使用其方法。它还展示了如何使用setPriority(int i)方法来更改枚举的变量。
  2. usingEnumValueOf()展示了如何使用java.util.Enum valueOf(enumType, name)通过字符串创建枚举对象。如果指定的枚举类型没有具有指定名称的常量,或者指定的类对象不代表枚举类型,则会抛出IllegalArgumentException。如果任何参数为null,也会抛出NullPointerException
  3. usingEnumValues()方法展示了values()方法的用法,该方法按照它们声明的顺序返回包含枚举所有值的数组。请注意,这个方法由java编译器为每个枚举自动生成。在java.util.Enum类中找不到values()的实现。
  4. usingEnumInSwitch()方法展示了如何在switch case中使用枚举常量。
  5. usingEnumMap()方法展示了java.util.EnumMap的用法,它是Java 1.5集合框架中引入的。EnumMap是用于枚举类型键的Map实现。枚举映射中的所有键必须来自于单个枚举类型,在创建映射时指定,明确或隐含。我们不能将null用作EnumMap的键,并且EnumMap不是同步的。
  6. usingEnumSet() 方法展示了如何使用 java.util.EnumSet,它是用于枚举类型的 Set 实现。枚举集中的所有元素必须来自于指定的单个枚举类型,该类型在创建集时明确或隐式地指定。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