Java 1.5 发行版本增长了新的引用类型: 枚举, 在其以前,咱们使用枚举类型值的时候一般是借助常量组成合法值的类型,例如表示光的三原色:红黄蓝的代码表示多是如下这样的。java
/*******************光的三原色*********************/
public static final int LIGHT_RED = 1;
public static final int LIGHT_YELLOW = 2;
public static final int LIGHT_BLUE = 3;
/*******************颜料的三原色*********************/
public static final int PIGMENT_RED = 1;
public static final int PIGMENT_YELLOW = 2;
public static final int PIGMENT_BLUE = 3;
复制代码
可是这样使用功能是受限的,好比不能知道对应枚举的个数等。幸亏,Java 1.5引入了枚举类型Enum。设计模式
使用枚举类型将前面的使用常量方式调整以下:数组
public enum LighjtOriginColorEnums {
RED,
YELLOW,
BLUE
}
public enum PigmentOriginColorEnums {
RED,
YELLOE,
BLUE;
}
public static void main(String[] args){
for(LighjtOriginColorEnums ele : LighjtOriginColorEnums.values()){
System.out.println(ele + " int value is: " + ele.ordinal());
}
}
复制代码
输出结果为:安全
RED int value is: 0
YELLOW int value is: 1
BLUE int value is: 2
复制代码
枚举类型的 ordinal()
方法,能够获得枚举的int整型值,该方法在 Enum
中定义,是一个不可覆盖的方法:bash
public final int ordinal() {
return ordinal;
}
复制代码
该方法返回枚举的 ordinal
属性值, 该值默认是枚举在其定义中的未知的索引值, 从0开始,即 RED.ordinal = 0, YELLOW.ordinal = 1, BLUE.ordinal = 2。ordinal的值大都数状况下是不会用到的。app
关于枚举的说明:ide
枚举是不能实例化的,只能声明后,再使用post
枚举不能实例化,因此是单例的性能
同理,枚举也是线程安全的ui
因此可使用枚举实现单例模式
若是声明一个参数类型为 LighjtOriginColorEnums
,则能够保证传递到该方法的任何非 null 的参数必须为 LighjtOriginColorEnums 中的三个枚举值之一。
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
复制代码
/** * enum classes cannot have finalize methods. */
protected final void finalize() { }
复制代码
/** * prevent default deserialization */
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
复制代码
正由于枚举具备这些特性,因此咱们可使用枚举实现友好的单例模式,请见: 【设计模式】你的单例模式真的是生产可用的吗?
看一个简单的示例,表示一个互金公司的金额项:本金、利息、手续费、滞纳金 的枚举: acquireOffsetItemByCode(String code)
方法能够根据枚举的code属性,得到枚举。
public enum OffsetItemEnums {
/** * 手续费 */
offsetitem_fee("fee", "手续费"),
/** * 滞纳金 */
offsetitem_penalty("penalty", "滞纳金"),
/** * 利息 */
offsetitem_int("int", "利息"),
/** * 本金 */
offsetitem_principal("principal", "本金"),
;
private String code;
private String desc;
private OffsetItemEnums(String code, String desc) {
this.code = code;
this.desc = desc;
}
public static OffsetItemEnums acquireOffsetItemByCode(String code) throws Exception{
for( OffsetItemEnums ele : OffsetItemEnums.values() ){
if( ele.code.equalsIgnoreCase(code) ){
return ele;
}
}
throw new Exception("Error code:" + code);
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
复制代码
在 Effective Java 第二版中的第30条定律中,举例了一个场景,如实现四则运算。
public enum Operation {
PLUS, MINUS, TIMES, DIVIDE;
double apply(double x, double y) throws Exception{
switch (this){
case PLUS:
return x + y;
case MINUS:
return x - y;
case TIMES:
return x * y;
case DIVIDE:
return x / y;
}
throw new Exception("Unknown op: " + this);
}
}
复制代码
这段代码可行, 可是须要 throw 一个Exception,若是没有抛出一个异常,就编译不过了,可是其实是不可能执行到最后一行代码的。还存在一个问题是,若是新增了枚举常量,可是忘记给switch添加相应的条件,枚举仍然能够编译,可是运行时不会获得指望的结果。
咱们发现一种更好的实现,是给枚举添加一个抽象的方法 apply:
public enum OperationGraceful {
PLUS, MINUS, TIMES, DIVIDE;
abstract double apply(double x, double y);
}
复制代码
此时编译错误: Class 'OperationGraceful' must either be declared abstract or implement abstract method 'apply(double, double)' in 'OperationGraceful' 意思是每个枚举常量必须实现声明的抽象方法:
public enum OperationGraceful {
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);
}
复制代码
如此一来,就不会在新增一个枚举值后,遗漏掉前面示例的switch分支逻辑,即便忘记了,编译器也会提醒,您须要实现抽象方法的规约。
特定于常量的方法实现能够与特定于常量的数据结合起来:
public enum OperationGracefulField {
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;
}
};
// 属性
private String symbol;
OperationGracefulField(String symbol){
this.symbol = symbol;
}
abstract double apply(double x, double y);
}
复制代码
在五个工做日中,正常超过8个小时,就会产生加班工资(固然现实实际上是不可能的,万恶的资本主义丿_\)。周末所有算加班。加班费按基本工资的一半计算。
public enum PayrollDay {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY;
private static final int HOUR_PER_SHIFT = 8;
double pay(double hourseWorked, double payRate){
double basePay = hourseWorked * payRate;
double overtimePay;
switch (this){
case SATURDAY:
case SUNDAY:
overtimePay = hourseWorked * payRate / 2;
default:
overtimePay = hourseWorked <= HOUR_PER_SHIFT
? 0 : (hourseWorked - HOUR_PER_SHIFT) * payRate / 2;
break;
}
return basePay + overtimePay;
}
}
复制代码
这个程序能够运行,达到基本的业务需求,可是从维护角度来看,比较危险,假设将一个元素添加到枚举中,或许是一个表示假期天数的特殊值,可是很是不幸,忘记了在switch分支添加代码区分,虽然程序能够编译,可是实际运行时可能会出现尴尬的结果,好比假期也计算了工资,带薪假期忘了计算工资等等。
咱们能够根据是否工做日、双休日,将加班工资计算移到一个私有的嵌套枚举中,而后将这个 策略枚举 的实例传递到 PayrollDay 枚举的构造器中。 以后 PayrollDay 的加班费计算规则委托给策略枚举, PayrollDay 就不须要switch 语句或者特定于常量的方法了。
public enum PayrollDayStrategy {
MONDAY(PayType.WEEKDAY),
TUESDAY(PayType.WEEKDAY),
WEDNESDAY(PayType.WEEKDAY),
THURSDAY(PayType.WEEKDAY),
FRIDAY(PayType.WEEKDAY),
SATURDAY(PayType.WEEKEND),
SUNDAY(PayType.WEEKEND);
private final PayType payType;
PayrollDayStrategy(PayType payType){
this.payType = payType;
}
private enum PayType{
WEEKDAY{
@Override
double overtimePay(double hrs, double payRate) {
return hrs <= HOUR_PER_SHIFT ?
0 : (hrs - HOUR_PER_SHIFT) * payRate / 2;
}
},
WEEKEND {
@Override
double overtimePay(double hrs, double payRate) {
return hrs * payRate /2;
}
};
private static final int HOUR_PER_SHIFT = 8;
abstract double overtimePay(double hrs, double payRate);
double pay( double hoursWork, double payRate ){
double basePay = hoursWork * payRate;
return basePay + overtimePay(hoursWork, payRate);
}
}
}
复制代码
须要一组固定常量的时候
例如: 行星、一周的天数等等
若是多个枚举常量同时共享相同的行为, 则考使用虑策略枚举
EnumSet
类用来有效地表示从单个枚举类型中提取的多个值的多个集合,实现了 Set 集合,提供了丰富的功能和类型安全性。
public class EnumSetDemo {
public enum Style{
BOLD,
ITALIC,
UNDERLINE,
STRIKETHROUGH
}
public void applyStyle(Set<Style> styles){
// TODO
}
public static void main(String[] args){
EnumSetDemo enumSetDemo = new EnumSetDemo();
enumSetDemo.applyStyle(EnumSet.of(Style.BOLD, Style.ITALIC));
}
}
复制代码
EnumSet 提供了不少静态方法用于建立集合。
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2) {
EnumSet<E> result = noneOf(e1.getDeclaringClass());
result.add(e1);
result.add(e2);
return result;
}
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
复制代码
其中 RegularEnumSet
、JumboEnumSet
是JDK自带的EnumSet的实现类。 若是底层的枚举类型有64个或者更少的元素————其实大都数如此,整个 EnumSet 就用单个long来表示,性能较好。
自从咱们知道 Enum 存在一个 ordinal 方法以后,可能对其使用就跃跃欲试了,好比,有一个用来表示一种烹饪的香草:
public enum Herb {
,
;
public enum Type{
ANNUAL, PERENNIAL, BIENNIAL
}
private String name;
private Type type;
Herb(String name, Type type){
this.name = name;
this.type = type;
}
@Override
public String toString() {
return name;
}
}
复制代码
场景 : 如今假设有一个香草的数组,表示一座花园中的植物,可是想要按照类型进行组织后将这些植物列出来。 分析: 咱们发现,须要按类型分类,而后列出来。能够用做映射,恰好有一个 EnumMap 的类能够达到这样的目的。
public enum Herb {
A("A", Type.ANNUAL),
B("B", Type.BIENNIAL),
AA("AA", Type.ANNUAL),
BB("BB", Type.BIENNIAL),
C("C", Type.PERENNIAL),
;
public enum Type{
ANNUAL, PERENNIAL, BIENNIAL
}
private String name;
private Type type;
Herb(String name, Type type){
this.name = name;
this.type = type;
}
@Override
public String toString() {
return name;
}
public static void main(String[] args){
// 这是一个花园,栽种有各类类型的香草
Herb[] garden = new Herb[]{Herb.A, Herb.B, Herb.AA, Herb.BB, Herb.C};
// EnumMap 构造器须要指定 class 做为类型参数
Map<Herb.Type, Set<Herb>> herbsByType =
new EnumMap<Type, Set<Herb>>(Type.class);
for (Type t : Herb.Type.values()){
// 按类型分类
herbsByType.put(t, new HashSet<Herb>());
}
// 开始对花园处理
for (Herb h : garden){
herbsByType.get(h.type).add(h);
}
// 输出分类信息
System.out.println(herbsByType);
}
}
复制代码
枚举还能够实现接口,用于实现扩展功能。
改装前面的四则运算案例:
// 接口
package org.byron4j.cookbook.javacore.enums;
public interface OperationI {
public double apply(double x, double y);
}
// 实现接口的枚举
package org.byron4j.cookbook.javacore.enums;
public enum BasicOperation implements OperationI{
PLUS("+"){
@Override
public double apply(double x, double y) {
return x + y;
}
}, MINUS("-") {
@Override
public double apply(double x, double y) {
return x - y;
}
}, TIMES("*") {
@Override
public double apply(double x, double y) {
return x * y;
}
}, DIVIDE("/") {
@Override
public double apply(double x, double y) {
return x / y;
}
};
// 属性
private String symbol;
BasicOperation(String symbol){
this.symbol = symbol;
}
}
复制代码
参考资料: