枚举是一种很常用的java功能,他可以将一组具名的值的有限集合创建伟一种新的类型,而且这些值可是作为常规的程序组件使用。我们在平常开发中只是简单的使用枚举,例如通过枚举来定义一组常量。下面我们通过进一步的探索来更深入的了解枚举这个工具。
枚举的特性
作为程序员,一言不合就上代码:
//:Fruit.java
enum Fruit {
APPLE, PEAR, ORANGE;
public String toString() {
return "# " + ordinal() + " " + name();
}
}
//:FruitTest.java
public class FruitTest {
public static void main(String[] args) {
for(Fruit fruit : Fruit.values()) {
System.out.println("ordinal: " + fruit.ordinal());
System.out.println(fruit.compareTo(Fruit.PEAR) + " ,");
System.out.println(fruit.equals(Fruit.PEAR) + " ,");
System.out.println(fruit.toString());
System.out.println("-------------------");
}
}
}
运行结果如下!
执行结果
我们可以看出来枚举有以下特性:
- 我们可以像使用类一样使用枚举,覆盖toString()方法;
- 如果需要在枚举里写方法,需要在最后一个定义的枚举值后面加分号;
- values()静态方法返回enum实例的数组;
- 每个枚举值都有一个编号,从0开始,通过ordinal()方法返回;
- 枚举实现了Comparable接口,还有equals方法;
- name()方法返回实例声明时的名字。
还有一些看不出来的特性:
- 枚举不能被继承;
- 定义枚举时可以实现接口,但是不能继承类。
探寻背后的原因
将Fruit.class文件执行以下javap反编译一下:
反编译Fruit
看看反编译出来的代码:
- 枚举本质上就是一个类,因为是用class声明的;
- 枚举继承了java.lang.Enum类,所以不能再继承其他的类了,因为java是单继承的,但是还可以实现接口的;
- 枚举是被final修饰的类,所以枚举不能被继承;
既然枚举是继承了java.lang.Enum类,那我们来看一下Enum类的结构:
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
Enum结构
这也正好证实了枚举实现了Comparable接口的说法。
并且Enum类定义了ordinal() 方法,所以我们可以直接调用。
不过这里细心的人可能发现了一个奇怪的点:上面定义的Fruit枚举反编译出来有一个values()方法,但是这个方法我们既没有在Fruit里面定义,Enum类中也没有定义,那么这个方法是那里来的呢?
答案是values()方法是由编译器添加的,在创建Fruit的过程中,编译器还添加了valueOf()方法,该方法不同于Enum中的valueOf()方法,Enum中的valueOf方法需要两个参数,而编译器新增的valueOf方法则只需要一个参数。
枚举的用途
枚举最常用的就是被定义成一组值当作常量使用,而且枚举也是支持switch语句的,例如:
enum Color{
RED,GREEN,BLUE;
}
public class Hello {
public static void main(String[] args){
Color color=Color.RED;
int counter=10;
while (counter-->0){
switch (color){
case RED:
System.out.println("Red");
color=Color.BLUE;
break;
case BLUE:
System.out.println("Blue");
color=Color.GREEN;
break;
case GREEN:
System.out.println("Green");
color=Color.RED;
break;
}
}
}
}
但是更多程序员喜欢用static final去定义一组常量使用,例如:
class IntEnum {
public static final int APPLE = 0;
public static final int PEAR = 1;
public static final int ORANGE = 2;
}
但是通过枚举的方式与通过static final的方式相比有以下优点:
- 由static final实现的枚举很难保证安全性,即当调用不在枚举范围内的数值时,需要额外的维护,而枚举方式则不存在此问题;
- 枚举方式更利于log的查看。
但是枚举方式相比与static final方式相比也是有缺点的:那就是枚举会占用更大的内存。也正因为如此,谷歌推荐在安卓开发中尽量使用static final的方式代替枚举。
其实除此之外枚举还有个更巧妙的用处:单例模式。
用枚举实现单例模式的代码简单到你不敢相信自己的眼睛:
enum Sington {
INSTANCE
}
对,就是这么简单,你就实现了一个线程安全,保证单例,自由序列化的单例模式。下面就来说说为什么。
1,通过前面的探索我们直到枚举就是通过class实现的,所以我们是可以自由扩展成员变量和方法的;
2,enum有且仅有私有构造方法(枚举构造方法的private可以省略),这恰好契合单例模式的要求;
3,通过前面反编译枚举的class文件我们知道:枚举量的实现其实是public static final T 类型的未初始化变量。如果枚举量有伴随参数并且手动添加了构造器,那么将会解析成一个静态的代码块在类加载时对变量进行初始化。这就类似于饿汉式的单例模式,从而式线程安全的;
4,每一个枚举类型和枚举变量在JVM中都是唯一的,即Java在序列化和反序列化枚举时做了特殊的规定,枚举的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法是被编译器禁用的,所以枚举天然可以防止被反序列化攻击。
到此为止就算是完成了一次浅尝则止的探索,其实枚举还有其他一些用途比如:责任链模式,多路分发等,与enum相关的还有EnumSet,EnumMap等这里就不展开了,如果有兴趣可以自行阅读Thinking in Java的枚举类型相关的章节。
网友评论