枚举在很多编程语言中都有,例如C/C++,但Java直到JDK1.5才增加这个特性,至于为什么那么晚,我就不得而知了。那什么是枚举呢?在维基百科上有如下定义:在数学和计算机科学理论中,一个集的 枚举 是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY等。
Java中各种组件都是类,枚举也不例外。要创建枚举类,只需要将普通类的class关键字替换成enum关键字即可,如下所示:
public enum ColorEnum { RED("RED", 0), BLUE("BLUE", 1), BLACK("BLACK",2), WHITE("WHITE", 3); ColorEnum(String name, int code) { this.name = name; this.code = code; } private String name; private int code; //getter and setter } 复制代码
枚举类也可以有成员变量、构造器、实例方法、静态方法,但构造器只能是私有的,即外部程序无法使用构造器来构造枚举实例,只能在实例类内部创建,例如RED("RED",0)的写法就是在调用构造器创建枚举实例,而且这个实例是单例的,这也是为什么有的文章说使用枚举来实现单例模式是最简单的(虽然是最简单,但并不是最实用的,因为可读性较差)。写一个类,肯定是要使用的,下面代码展示了枚举类的使用:
public class Test { public static void main(String[] args) { System.out.println(ColorEnum.BLACK); //直接调用实例 System.out.println(ColorEnum.BLACK.getName()); //调用实例的getName()方法 System.out.println(ColorEnum.BLACK.getCode()); //调用实例的getCode()方法 System.out.println(ColorEnum.valueOf("BLACK")); //valueOf()方法根据枚举的名字获取枚举实例 System.out.println("----------------------------"); for (ColorEnum colorEnum : ColorEnum.values()) { //values()方法返回该枚举类的所有枚举实例 System.out.println(colorEnum.getName()); System.out.println(colorEnum.ordinal()); //ordinal返回该枚举实例在枚举类中声明的顺序,从0开始 } } } 复制代码
注释写的比较清楚了,唯一让人迷惑的就是valueOf(String)方法,该方法接受的参数是枚举实例的名字,注意这里的名字不是指的name成员变量,而是该实例本身的名字,例如在ColorEnum中的RED("RED",0)声明,最外面的RED就是该实例本身的名字,字符串"RED"只是该枚举类的一个成员变量,这里不理解没关系,等会看源码的时候就明白了。
除了简单使用之外,我们还可以在枚举类中加入抽象方法,每个实例都必须实现这个抽象方法,否则会编译失败,如下所示:
//其他代码和原先完全一样 RED("RED", 0) { @Override public void abstractMethod() { System.out.println("RED's method"); } }, BLUE("BLUE", 1) { @Override public void abstractMethod() { System.out.println("BLUE's method"); } }, BLACK("BLACK",2) { @Override public void abstractMethod() { System.out.println("BLACK's method"); } }, WHITE("WHITE", 3) { @Override public void abstractMethod() { System.out.println("WHITE's method"); } }; public abstract void abstractMethod(); 复制代码
代码中声明了abstractMethod()抽象方法,每个枚举实例都必须实现这个方法且每个枚举实例都可以有自己的实现,这是枚举类灵活性的体现。这个特性在有些场景下非常有用,例如如果有一个枚举类用来表示四则运算的操作,使用抽象方法,该方法有两个参数,然后不同的运算操作就可以根据自己的特性实现不同的运算。
枚举其实还是很多灵活的用法,在此不再多说了。
Enum类是所有枚举类的父类,实现了Comparable和Serializable接口,具有可比较和可序列化的能力。其源码如下所示:
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { //name字段表示枚举实例的名字 private final String name; public final String name() { return name; } //ordinal字段表示枚举实例在枚举类中声明的顺序,从0开始 private final int ordinal; public final int ordinal() { return ordinal; } //构造器 protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; } public String toString() { return name; } public final boolean equals(Object other) { return this==other; } public final int hashCode() { return super.hashCode(); } //默认不支持clone protected final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } public final int compareTo(E o) { Enum<?> other = (Enum<?>)o; Enum<E> self = this; if (self.getClass() != other.getClass() && // optimization self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal; } @SuppressWarnings("unchecked") public final Class<E> getDeclaringClass() { Class<?> clazz = getClass(); Class<?> zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper; } public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { T result = enumType.enumConstantDirectory().get(name); if (result != null) return result; if (name == null) throw new NullPointerException("Name is null"); throw new IllegalArgumentException( "No enum constant " + enumType.getCanonicalName() + "." + name); } protected final void finalize() { } //默认不支持反序列化,反序列化会破坏单例模式 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("can't deserialize enum"); } private void readObjectNoData() throws ObjectStreamException { throw new InvalidObjectException("can't deserialize enum"); } } 复制代码
先来看看两个成员变量name和ordinal,name就是之前提到的枚举实例的名字,ordinal就是枚举实例在枚举类中声明的顺序,从0开始。然后就是valueOf()方法,该方法根据枚举实例的名字和枚举实例的类对象来获取对应的枚举实例,在我们使用的时候没有传入enumType,是因为在创建枚举类的时候,Java帮我们加入了一个重载版本,我们一般使用该重载版本即可。最后再看看readObject()方法,默认情况下,枚举实例是可以序列化的,但是不能反序列化,因为反序列化的时候会调用readObject方法,默认情况下,该方法会直接抛出异常,阻止反序列化,我们可以通过重写该方法来打开反序列化的开关,但要小心一些,因为反序列化操作会破坏枚举实例的单例特性,可能会导致虚拟机中的枚举实例不唯一。
其他方法例如equal,compareTo什么的就不多说了,都是套路。
枚举也是一种很重要的组件,功能很强大,灵活,但很多开发者可能会小看他,认为其不过就是声明了一些枚举常量而已。这确实是枚举最根本的作用,但实际上,Java枚举还有很多其他强大的功能,例如可以声明抽象方法,可以轻而易举的保证线程安全,保证单例等等。