对于枚举这个类,我之前一直以为它没什么用处,后来在做项目的过程中遇到枚举的时候才发现枚举这个东西居然这么强大...
正如Java编程思想中有这么一句话:“有时正因为有它,你才能够‘优雅而干净’的解决问题”;
1、枚举类的常规用法:
public enum Color {
RED, BLUE, BLACK;
public static void main(String[] args) {
for (Color main : Color.values())
{
System.out.println(main);
}
}
}
//输出:
//RED
//BLUE
//GREEN
枚举的中的values( ) 方法,可以遍历enum实例。values方法返回enum实例的数组,而且该方法的顺序严格保持和enum中实例的声明顺序相同。因此可以在循环中使用values方法返回的数组。
2、enum可以作为switch的判断条件
一般来说,switch中放的都是基本类型的数据(java中也可以放字符串),枚举天生就有整数的性质,所以在switch中也可以使用enum类的实例来作为判断条件。一般情况下,我们必须使用enum类来描述一个枚举类型,但是在case语句中,不必这么做,示例如下:
Color color = Color.BLACK;
switch (color)
{
case RED:
System.out.println("the color is red");
break;
case BLUE:
System.out.println("the color is blue");
break;
case BLACK:
System.out.println("the color is black");
break;
}
在这里面,编译器不会抱怨没有default语句,但是这并不是因为上面这个例子中每一个枚举实例都有一个case语句,因为如果现在你将其中的一个case语句注释掉,编译器也同样不会报错,因为编译器并不会强制让每一个枚举实例都有一个对应的case。这就意味着你必须自己覆盖掉所有可能的分支。
3、在enum中添加新方法
对于枚举,除了不能继承自一个enum类以外,我们基本上可以将一个enum类看做一个普通的类。也就是说,我们可以在一个enum类中添加方法。甚至可以有主方法main();
一般来说,我们有时候需要对于每一个枚举实例都有对他自己的描述,而不仅仅是toString( )来返回它的名字这么简单。比如我想定义一个描述一个周内每一天的枚举类型,然后一个实例是星期一,我想说星期一是一周的第一天。要想实现这个功能的话明显一个toString( )是做不到的。为此,我们可以定义一个构造方法,用来添加一些额外的信息,如下:
public enum Weeks {
MONDAY("the day of the first day"),
TUESDAY("the day of the second day"),
WEDNESDAY("the day of the third day"),
THURSDAY("the day of the fourth day"),
FIRDAY("the day of the fifth day");
private String description;
private Weeks(String description)
{
this.description = description;
}
public static void main(String[] args) {
for(Weeks weeks : Weeks.values())
{
System.out.println(weeks+":"+weeks.description);
}
}
}
输出为:
MONDAY:the day of the first day
TUESDAY:the day of the second day
WEDNESDAY:the day of the third day
THURSDAY:the day of the fourth day
FIRDAY:the day of the fifth day
不过在这里需要注意:如果想自定义方法,必须保证最后一个enum实例要加分号,同时,Java中要求必须先定义enum实例,才能在定义属性和方法,否则会报错。
还有一个需要注意的地方是:枚举类的实例只能在枚举类的内部实现,一旦枚举类的定义结束,编译器将不再允许我们使用枚举类的构造器来创建任何实例了。
4、关于enum类中的values( )方法
其实,我们在创建一个enum类的时候,编译器就会为我们生成一个相关的类,继承自java.lang.Enum这个类。而对于每一个enum实例,都会点用Enum中的Enum(String name, int ordinal)的构造方法。其中name就是实例的名字,ordinal就是顺序的意思。
但是当我们点开源码的时候会发现,Enum中并没有一个叫values( )的方法:
那么这个方法又是从哪里来的呢???
答案是:values()方法是编译器像enum类中添加的一个static方法,就以上面的例子Color来看,在创建Color的时候其实编译器向其中添加了两个方法,分别是:values 和 valuesOf( ); 在这里看起来可能有些迷惑,因为Enum类中不是已经有valuesOf方法了么。没错,但是在Enum中的valuesOf( )方法是两个参数的,而新添加的方法只有一个参数。
同时,编译器还会将Color方法标记位final方法,所以它不能被继承。
由于values方法是由编译器插入到enum中的static方法,所以如果你将enum向上转型为Enum,那么将不能调用values方法来遍历enum实例。不过在Class类中有一个getEnumConstants( )方法,即便Enum中没有values方法,也可以通过它遍历enum的实例。
enum Color {
RED, BLUE, BLACK;
}
public class Test
{
public static void main(String[] args) {
Enum e = Color.RED;
for (Enum color : e.getClass().getEnumConstants())
{
System.out.println(color);
}
}
}
由于getEnumConstants( )方法是Class类的方法,所以也可以用在其他类上。
5、enum实现接口
在前面已经提到过,每个enum类,编译器都会让它继承Enum方法,在java中,类又不支持多继承,所以enum类不能再去继承别的类。但是java支持一个类可以实现多个接口。所以enum可以实现多个接口。在java编程思想中说到,实现接口是使enum类子类化的唯一方法。比如下面例子:
在实现接口后,可以通过enum的实例来调用其中的方法。
6、使用接口组织枚举
通过上面的例子,说枚举不能继承一个类,但是有时候我们又想将一个enum的实例通过子类进行分类组织。举个栗子:我们现在想让enum类表示不同种类的食物,但是我们还想让这些枚举类型都依然保持Food类型。这时候可以使用接口将其组织起来,这么实现:
如果这个时候有很多类型,我们还可以使用 “枚举的枚举”,如下:
interface Food {
enum SftapleFood implements Food{
RICE, NOODLES, DUMPLINGS, MANTOU
}
enum JunkFood implements Food{
LATIAO, BINGGAN
}
enum Drink implements Food{
COFFEE, TEA, WATER
}
}
enum FoodType {
SFTAPLE_FOOD(Food.SftapleFood.class),
JUNK_FOOD(Food.JunkFood.class),
DRINK(Food.Drink.class);
private Food[] val;
FoodType(Class<? extends Food> kind)
{
val = kind.getEnumConstants();
}
public Food[] getFood()
{
return val;
}
}
public class Test
{
public static void main(String[] args) {
for (Food food : FoodType.JUNK_FOOD.getFood())
{
System.out.println(food);
}
}
}
/**
* 输出结果:
* LATIAO
* BINGGAN
*/
7、枚举实现单例模式
在《Effectiov Java》一书中,对于枚举实现单例模式的方式推崇备至。
使用枚举实现单例模式的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。
我们先来看看懒汉式单例模式的实现(使用双重检验锁):
在来看看枚举类型的单例模式:
对于这两种方式的比较显而易见,为了让我们看起来不至于太过明显,我还在枚举的里面附加了一个方法。
枚举实现的单例除了简单之外,还有两个好处:
- 线程安全问题:因为懒汉式单例在写的时候如果没有双重检验锁,在多线程的时候会有线程安全的问题,如果使用枚举类,Java虚拟机在加载枚举类的时候,会使用ClassLoader的loadClass方法,这个方法使用了同步代码块来保证线程安全问题。
- 避免反序列化破坏单例:因为枚举类的反序列化并不通过反射实现。