目录
前言
一、什么是枚举类型
二、枚举类型的使用
三、使用枚举实现单例模式
总结
前言
在平时的开发过程中,经常会遇到需要定义一组有限的常量的情况,这时候能使用的方法为:定义一个常量类或者接口,这种方法存在诸多缺陷,比如在类或者接口中定义了多个值为一致的常量,这样就会导致表达的意思混淆,甚至可能出现==来比较两个表示不同意思的常量。同时接口或者类可以被实现和继承并进行修改,通过获取元素的方式也能够修改其中元素的值,是非常不安全的。
Java 5新增了枚举类型,通过枚举类型可以更好得实现表达一组常量,枚举类型是一个特殊得类类型,但是却比普通的类要更安全、便捷。下面就详细介绍一下枚举类型
一、什么是枚举类型
枚举类型其实就是一个特殊的类类型,本质上是一种继承java.lang.Enum类,是引用数据类型。下面定义一个枚举类型
public enum MyTestEnum {
MONDAY(1, "xing qi yi"),
TUESDAY(2, "xing qi er"),
WEDNESDAY(3, "xing qi sang"),
THURSDAY(4, "xing qi si"),
FRIDAY(5, "xing qi wu"),
SATURDAY(6, "xing qi liu"),
SUNDAY(7, "xing qi tian");
private int code;
private String desc;
MyTestEnum(int code, String desc) {
this.code=code;
this.desc=desc;
}
}
枚举类只有一个私有类型的构造方法,所以不能够创建对象。通过javac对该枚举类进行编译,然后通过javap对该类进行反编译,看看结果是什么
可以看到,生成一个继承java.lang.Enum的类,枚举类里面的元素都会生成定义的枚举类类型的对象,生成的类为final修饰,所以不能进行继承,保证了枚举类型的安全。所有枚举内的每一个元素都是一个对象。
二、枚举类型的使用
枚举类型除了可以列举各种类型以外,还可以使用switch语句定义各个枚举所要执行的操作,例如有这个枚举,定义了加减乘除的符号,并且定了了加减乘除的运算
public enum OperationEnum {
PLUS,
MINUS,
TIMES,
DIVIDE;
// 定义加减乘除算法
double apply(double x, double y) {
switch (this){
case PLUS:
return x+y;
case MINUS:
return x-y;
case TIMES:
return x*y;
case DIVIDE:
return x/y;
}
throw new AssertionError("Unknown op" + this);
}
}
这样使用起来看起来很方便,但是需要添加新的枚举的时候,如果忘记在case里面新增枚举类型之后就会导致新添加的枚举常量无法匹配到运算公式而抛出错误。
可以通过在枚举类中添加一个抽象方法,这样每当添加一个枚举的时候,都要求去实现这个抽象方法,不然就会编译出错,这样就不会导致忘记添加规则的情况了。
public enum OperationEnum {
PLUS() {
@Override
double apply(double x, double y) {
return x + y;
}
},
MINUS {
@Override
double apply(double x, double y) {
return x - y;
}
},
TIMES {
@Override
double apply(double x, double y) {
return x * y;
}
},
DIVIDE {
@Override
double apply(double x, double y) {
return x / y;
}
};
// 定义加减乘除算法
abstract double apply(double x, double y);
}
在枚举类中定义一个抽象方法可以很好的解决这个问题,但是有时候存在多个枚举类型值的计算方式是一样的,如果每个枚举都要去实现这个抽象方法,那么就会导致大量重复的代码。如下面这个枚举
public enum MyTestEnum {
// 周一至周五工作时间超过8个小时之后,就会按照小时提供加班费。周六周天工作都会按小时提供加班费
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY;
public static final int HOURS_PER_SHIFT = 8;
double pay(double hoursWorked, double payRate) {
// 基本工资
double basePay = hoursWorked * payRate;
// 加班工资
double overtimePay;
switch (this) {
case SATURDAY: case SUNDAY:
overtimePay=hoursWorked*payRate/2;
default:
overtimePay=hoursWorked>HOURS_PER_SHIFT?(hoursWorked-HOURS_PER_SHIFT)*payRate/2:0;
}
return basePay + overtimePay;
}
}
这种情况会switch语句会比抽象方法的方式更加的简洁方便,但是也存在弊端,比如现在需要添加法定节假日的薪资计算方法,此时就需要添加case,如果忘记添加case,则会导致计算薪资错误。这个时候就会联想到java中的抽象,可以跟类一样,将薪资计算的方法提取到一个专门计算薪资的类中,然后根据情况来调用计算薪资类的方法来计算薪资。枚举类型也支持这样的实现,通过在枚举中添加一个私有的嵌套枚举,专门来负责计算薪资的事情。
public enum MyTestEnumV2 {
// 周一至周五工作时间超过8个小时之后,就会按照小时提供加班费。周六周天工作都会按小时提供加班费
MONDAY(PayEnum.WEEKDAY), TUESDAY(PayEnum.WEEKDAY), WEDNESDAY(PayEnum.WEEKDAY),
THURSDAY(PayEnum.WEEKDAY), FRIDAY(PayEnum.WEEKDAY), SATURDAY(PayEnum.WEEKEND),
SUNDAY(PayEnum.WEEKEND);
private final PayEnum payEnum;
MyTestEnumV2(PayEnum payEnum) {
this.payEnum=payEnum;
}
double pay(double hoursWorked, double payRate) {
return this.payEnum.pay(hoursWorked, payRate);
}
// 计算工资的枚举
private enum PayEnum{
WEEKDAY {
@Override
double overtimePay(double hoursWorked, double payRate) {
return hoursWorked>HOURS_PER_SHIFT?(hoursWorked-HOURS_PER_SHIFT)*payRate/2:0;
}
},
WEEKEND {
@Override
double overtimePay(double hoursWorked, double payRate) {
return hoursWorked*payRate/2;
}
};
// 计算加班工资的方法,因为跟日期有关,所以定义为抽象方法,根据不同的日期去实现
abstract double overtimePay(double hoursWorked, double payRate);
public static final int HOURS_PER_SHIFT = 8;
// 计算工资都是基本工资+加班工资
double pay(double hoursWorked, double payRate) {
double basePay=hoursWorked*payRate;
return basePay+overtimePay(hoursWorked, payRate);
}
}
}
这样再需要添加新的节假日计算方式的时候,就必须要添加一个PayEnum的元素,便不会遗漏添加这种事情发生了。
三、使用枚举实现单例模式
为什么要通过枚举去实现单例模式,实现单例模式的方法有许多种,例如:饿汉模式、懒汉模式(双重锁检测)、静态内部类等方式。但是这些模式都存在一些公共的问题,例如:
- 如果通过反射去获取私有的构造方法来创建对象则创建的对象并非第一次创建的对象
- 如果类实现了Serverlization接口,反序列化之后都会创建一个新的对象
基于上面两个问题,通过枚举类型创建单例就成为了创建单例模式的最佳方法
public enum Singleton {
INSTANCE;
public Singleton singleton() {
return INSTANCE;
}
}
使用枚举方式可以完全避免反序列化而产生的创建的对象不止一个的问题,下面是《Effective Java》中的一段话
通过反射获取私有的构造方法,然后调用newInstance方法来创建实例在枚举类型上也是行不通的,看一下newInstance的源码
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
...
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
...
T inst = (T) ca.newInstance(initargs);
return inst;
}
如果是枚举类型调用newInstance方法则会直接报错。
总结
本文介绍了枚举类型的原理,以及枚举类型的一些优点,所以在开发过程中能够使用枚举类型去替代的尽量使用枚举类型。