Java 枚举类型的使用
一、为什么要用枚举类
1)出于类型安全考虑
有时候可能会使用静态常量来表示一些变量。如下:
public static final int MAN = 0;
public static final int WOMAN = 1;
这样的性别定义实际上是一个整型数据,有些许不当的地方:
- 其一,这些变量完全可用来做加减运算,当然我们原意并非如此;
- 其二,意义不明,当我们debug的时候,本来希望输出‘男’,结果输出0。于是我们不得不去查询 0 所表示的含义。
2)代码更优雅
一个大一些的程序里面,可能要用到成百上千的静态常量,如果全写在一个文件里面,容易造成命名混淆,程序读起来也比较麻烦。
3)枚举类能方便我们定义自己想要的类型
枚举便于记忆和使用,并且相当于一个接口。使用时只需要封装内部的数据类型,并且限定数据域。而且对于不同的枚举变量,可以调用不同的处理方法(实现枚举类的抽象方法可以做到这一点)。
二、枚举类的定义及其内部方法讲解
1、枚举类基础
public enum Week {
//本文的枚举类变量,枚举类实例,name属性指的就是MONDAY
//这类的变量
MONDAY(0,"星期一"),
TUESDAY(1,"星期二"),
WEDNESDAY(2,"星期三"),
THURSDAY(3,"星期四"),
FRIDAY(4,"星期五"),
SATURDAY(5,"星期六"),
//最后一个类型必须要用分号结束
SUNDAY(6,"星期日");
private int num;
private String desc;
/**
* 构造方法必然是private修饰的
* 就算不写,也是默认的
*
* @param num
* @param desc
*/
private Week(int num, String desc) {
this.num=num;
this.desc = desc;
}
public String getDesc() {
return desc;
}
public int getNum() {
return num;
}
}
- 使用enum定义的枚举类默认继承了java.lang.Enum,实现了java.lang.Comparable接口,且不能继承其他类,也不可以被继承。但枚举类可以实现一个或多个接口。想深入学习必须挖Enum的源码。
- 枚举类的所有实例必须放在第一行显示,不需使用new,不需显示调用构造方法,每个变量都是public static final修饰的,最终以分号结束。在之后的反编译中,我们就可以理解枚举类其实也是颗语法糖。
- 枚举类的构造方法是私有的,默认的就是private,定义的时候不加也没事。
- switch()参数可以使用enum。这个在后文有详细代码。
- 非抽象枚举类默认是final的但定义的时候加上final却编译不通过。我们通过后续的反编译可以得到验证。
- 枚举类可以有抽象方法,但是必须在它的实例中实现。后续也会验证这一条。
2、name成员变量和常用方法
【1】name 成员变量
private final String name;
其实上面代码中的 MONDAY 就是 name 属性。
【2】枚举类的常用方法
- valueOf() 返回当前枚举类的name属性,如果没有,则throw new java.lang.IllegalArgumentException()。具体可以查看java.lang.Enum源码。
- values() 是编译器自动生成的方法,Enum中并没有该方法,返回包括所有枚举变量的数组。
- toString(),name() 很简单,两个方法一样,返回当前枚举类变量的name属性。如果默认的toString()不能满足需求,可以重新实现 toString() 方法,如下:
/**
* 用switch重写toString方法,提高代码健壮性
* @return
*/
@Override
public String toString() {
//switch支持Enum类型
switch (this) {
case MONDAY:
return "今天星期一";
case TUESDAY:
return "今天星期二";
case WEDNESDAY:
return "今天星期三";
case THURSDAY:
return "今天星期四";
case FRIDAY:
return "今天星期五";
case SATURDAY:
return "今天星期六";
case SUNDAY:
return "今天星期日";
default:
return "Unknow Day";
}
}
- ordinal() 枚举类会给所有的枚举变量一个默认的次序,该次序从0开始,是根据我们定义的次序来排序的。而ordinal()方法就是获取这个次序(或者说下标)。
- compareTo() 比较的是两个枚举变量的次序,返回两个次序相减后的结果。如下代码:
public static void main(String[] args) {
//通过values()获取枚举数组
Week[] weeks = Week.values();
//遍历Week枚举类
for (Week day : weeks) {
System.out.println("name:" + day.name() +
",desc:" + day.getDesc());
}
//不符合则抛出java.lang.IllegalArgumentException
System.out.println(Week.valueOf("MONDAY"));
//返回对应的name属性
System.out.println(Week.FRIDAY.toString());
//返回4,根据我们定义的次序,从0开始。如果在定义时调换FRIDAY
//的次序,返回的数字也会对应的变化
System.out.println(Week.FRIDAY.ordinal());
}
输出:
name:MONDAY,desc:星期一
name:TUESDAY,desc:星期二
name:WEDNESDAY,desc:星期三
name:THURSDAY,desc:星期四
name:FRIDAY,desc:星期五
name:SATURDAY,desc:星期六
name:SUNDAY,desc:星期日
今天星期一
今天星期五
4
3、含有抽象方法的枚举类
如果写抽象方法,枚举类的所有实例必须实现抽象方法。MONDAY本身就是一个AbstractWeek对象的引用。在初始化这个枚举类的时候,等同于执行的是AbstractWeek MONDAY= new AbstractWeek(0,"星期一")。然后用匿名内部类的方式实现getNextDay()。
/**
* 枚举类可以有抽象方法,但是必须在它的实例中实现
*/
public enum AbstractWeek {
MONDAY(0,"星期一") {
@Override
public AbstractWeek getNextDay() {
return TUESDAY;
}
}, TUESDAY(1,"星期二") {
@Override
public AbstractWeek getNextDay() {
return WEDNESDAY;
}
}, WEDNESDAY(2,"星期三") {
@Override
public AbstractWeek getNextDay() {
return THURSDAY;
}
}, THURSDAY(3,"星期四") {
@Override
public AbstractWeek getNextDay() {
return FRIDAY;
}
}, FRIDAY(4,"星期五") {
@Override
public AbstractWeek getNextDay() {
return SATURDAY;
}
}, SATURDAY(5,"星期六") {
@Override
public AbstractWeek getNextDay() {
return SUNDAY;
}
}, SUNDAY(6,"星期日") {
@Override
public AbstractWeek getNextDay() {
return MONDAY;
}
};
private int num;
private String desc;
AbstractWeek(int num,String desc) {
this.num = num;
this.desc=desc;
}
//一个抽象方法
public abstract AbstractWeek getNextDay();
public static void main(String[] args) {
String nextDay=AbstractWeek.MONDAY.getNextDay().toString();
System.out.println(nextDay);
}
}
编译后所有实例都会成为内部类,相当于每个实例用匿名内部类的形式实现getNextDay的方法。如:
AbstractWeek MONDAY= new AbstractWeek (){
@Override
public AbstractWeek getNextDay() {
return TUESDAY;
}
};
三、为什么说枚举类是语法糖
这个问题需要从原理角度阐述,我们通过反编译来查看AbstractWeek,可以发现继承了Enum,里面的所有成员变量都是public static final修饰的!而且values()方法是编译器生成的。其实每一个实例都是一个内部类,在项目路径下会生成AbstractWeek$1.class-AbstractWeek$7.class。每个内部类又都实现了getNextDay()方法。因此,定义枚举实例的时候代码如此的优雅,完全是语法糖给我们的甜头呀!
public abstract class AbstractWeek extends java.lang.Enum<AbstractWeek> {
public static final AbstractWeek MONDAY;
public static final AbstractWeek TUESDAY;
public static final AbstractWeek WEDNESDAY;
public static final AbstractWeek THURSDAY;
public static final AbstractWeek FRIDAY;
public static final AbstractWeek SATURDAY;
public static final AbstractWeek SUNDAY;
public static solution1.AbstractWeek[] values();
Code:
0: getstatic #2 // Field $VALUES:[Lsolution1/AbstractWeek;
3: invokevirtual #3 // Method "[Lsolution1/AbstractWeek;".clone:()Ljava/lang/Object;
6: checkcast #4 // class "[Lsolution1/AbstractWeek;"
9: areturn
......
}
四、枚举版单例与反射获取枚举对象
1、学会枚举创建单例模式
我们可以充分利用枚举默认构造方法私有化的性质来实现单例。由于里面的成员变量都是final修饰的,因此不会有线程不安全的问题。枚举类版的单例模式,就是这么简单。
public enum EnumSingleton{
INSTANCE;
}
2、反射获取枚举对象
// 1.得到枚举类对象
Class<?> clazz = AbstractWeek.class;
// 2.得到枚举类中的所有实例
Object[] enumInstances = clazz.getEnumConstants();
Method getDesc= clazz.getMethod("getDesc");
Method getNextDay= clazz.getMethod("getNextDay");
for (Object instance : enumInstances){
// 3.调用对应方法,得到枚举类中实例的值与实现的抽象方法
System.out.println("desc=" + getDesc.invoke(obj) + "; nextDay=" + getNextDay.invoke(obj));
}