枚举 enum
是JDK1.5加入的特性,平时编码中,可能更多的用 public static final
的修饰来表示不同类型的常量,当然,枚举以其直观,规范等特点,也不失为一个好的选择。总结一下枚举的用法,方便以后查看。总之首先要记住的就是,枚举是一个类。
枚举基础
一个简单的枚举类:
public enum Dream {
WIND, FLOWER, SNOW, MOON
}
所有枚举都是 java.lang.Enum
的子类
枚举本质上也是一个类,他只是编译器的一个语法糖,全天下所有枚举都是java.lang.Enum
的子类,也就是说,枚举类能像普通类一样添加成员变量和方法。
在枚举类 Dream
中,WIND, FLOWER, SNOW, MOON
等值实际上就是 java.lang.Enum
类的实例,跟接口一样,默认使用 public static final
修饰,每一个实例都是默认调用了 java.lang.Enum
中的构造方法实现的:
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
其中,name
就是枚举中值的名字,如 WIND
, ordinal
表示从 0
开始定义的顺序,如 WIND
的顺序就为 0
,MOON
的顺序为 3
。
枚举类的私有构造方法
枚举类中的构造方法只能是私有 private
的,强行添加非私有的构造方法,则无法通过编译。这说明,枚举类是不能被继承的,默认地,枚举类都是final
的,从枚举类存在的意义来看,这也是理所当然的。当然,枚举类本身也不能继承其他类,因为已经默认继承了 java.lang.Enum
类。
和普通类一样,枚举类中有一个默认的无参构造方法,当为枚举添加其他的构造方法时,默认的构造方法将失效。此时,在枚举中初始化值时,应显式调用构造方法:
public enum Dream {
WIND, FLOWER, SNOW(666), MOON(999);
int growthRing;
Dream() {
System.out.println(this.name() + " 被构造啦"); //方法name()是父类中的方法,返回当前实例名字
}
Dream(int growthRing) {
this.growthRing = growthRing;
System.out.println(this.growthRing + "圈");
}
}
通过 Class.forName
方法加载枚举类测试:
public static void main(String[] args) {
try {
Class.forName("com.gdut.topic.enum_demo.Dream");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
结果如下:
WIND 被构造啦
FLOWER 被构造啦
构造 666圈
构造 999圈
可见,四个实例分别调用了四次构造函数。
枚举类中的方法
枚举类中,除了可以像普通类中添加自定义的方法和成员变量,自身也默认实现了两个方法,他们分别是 values
方法和 valueOf
方法。使用javap反编译Dream.class
文件得到如下代码:
public final class com.gdut.topic.enum_demo.Dream extends java.lang.Enum<com.gdut.topic.enum_demo.Dream> {
public static final com.gdut.topic.enum_demo.Dream WIND;
public static final com.gdut.topic.enum_demo.Dream FLOWER;
public static final com.gdut.topic.enum_demo.Dream SNOW;
public static final com.gdut.topic.enum_demo.Dream MOON;
public static com.gdut.topic.enum_demo.Dream[] values();
public static com.gdut.topic.enum_demo.Dream valueOf(java.lang.String);
static {};
}
可以看到,除了对应枚举值的常量对象外,还有两个方法,并且他们是静态的。
values
方法返回一个包含全部枚举值的数组。如:
Dream[] dreams = Dream.values();
valueOf
方法根据名字返回枚举实例。如:
Dream SNOW = Dream.valueOf("SNOW");
父类中的方法
枚举类的父类 java.lang.Enum
中也有许多方法,最有用的是 toString
方法,他和 name
方法一样,返回枚举常量名。例如Dream.FLOWER.toString
将返回字符串 "FLOWER"
。
java.lang.Enum
的静态方法 valueOf
是 toString
的逆方法,他接收一个枚举类型的 class
对象和枚举常量的名字 name
,返回具体的枚举常量。如:
Dream MOON = Enum.valueOf(Dream.class, "MOON");
name
和 valueOf
方法其实就是JVM提供的对枚举的序列化与反序列化方法。
java.lang.Enum
还实现了 java.lang.Comparable
接口,即实现了 compareTo
方法,具体实现如下:
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;
}
看的出来,只是简单比较两个枚举常量定义时的顺序,并且不能重写这个方法。
抽象方法
枚举中可以定义抽象方法,当枚举类定义抽象方法或者实现接口时,就是抽象类,但不用显式的添加 abstract
关键字,由于枚举类无法继承,所以需要在实例化枚举常量时实现抽象方法,写法就跟匿名内部类的写法一样:
public enum Dream {
WIND{
@Override
public int getYear() {
return 10;
}
},
FLOWER{
@Override
public int getYear() {
return 20;
}
},
SNOW{
@Override
public int getYear() {
return 30;
}
},
MOON{
@Override
public int getYear() {
return 40;
}
};
public abstract int getYear();
}
有人说,这也是算是通过特殊的方法继承了枚举类了。
枚举用处
这是最直观的用处,相比于常量接口,具有直观规范的特点,比如有一个接收星期数的方法,用常量接口,可能传的值是0-6,但不能避免不小心传到其他的值,使用枚举作为参数,则不会出现这个问题,即可控性天然得到了保证。
枚举还可以用来作为 switch
语句的参数使用。
枚举类的 equals
方法,直接用 ==
操作符进行比较,并且不能重写。
用枚举实现单例
单元素枚举可用来实现单例,因为枚举中的实例都是被 static final
修饰的常量,只实例化一次,又因为他全部私有的构造方法,保证了无法从外部实例对象。
Java规范又指出,在枚举的序列化与反序列化上,是通过 name
和 valueOf
方法来实现的,也就是序列化枚举的名字,反序列化时根据名字查找对象。因此反序列化后的实例也会和之前被序列化的对象实例相同。
因此,用枚举实现单例,不失为一种好方法:
public enum Singleton {
INSTANCE;
public void doSomeThing() {
//...
}
}
总结
- 所有枚举都是
java.lang.Enum
的子类,枚举可以有成员变量和方法 - 枚举中的实例默认使用
public static final
修饰,并显示或隐式地调用构造方法初始化 - 枚举中的枚举常量应该写在枚举类中的开头
- 枚举只能有
private
的构造方法 - 枚举不能被继承,不能继承其他类,但可以实现接口,添加抽象方法
- 当枚举中有抽象方法时,枚举常量必须用类似匿名内部类的写法初始化
- 枚举的序列化和反序列化是用
name
和valueOf
方法,写入枚举名字或根据枚举名字查找对象实现的 - 根据枚举的特性,单元素枚举可以实现类似饿汉式的单例模式