枚举的使用
限定了值域,更安全了
在JDK5中新引入的枚举完美地解决了之前通过常量来表示离散量所带来的问题,大大加强了程序的可读性、易用性和可维护性,并且在此基础之上又进行了扩展,使之可以像类一样去使用,更是为Java对离散量的表示上升了一个台阶。因此,如果在Java中需要表示诸如颜色、方式、类别、状态等等数目有限、形式离散、表达又极为明确的量,应当尽量舍弃常量表示的做法,而将枚举作为首要的选择
enum关键字在 java5 中引入,表示一种特殊类型的类,其总是继承java.lang.Enum类
以这种方式定义的常量使代码更具可读性,允许进行编译时检查,预先记录可接受值的列表,并避免由于传入无效值而引起的意外行为
- 定义一个简单的枚举类型 pizza 订单的状态,共有三种 ORDERED, READY, DELIVERED状态:
publicenum PizzaStatus {
ORDERED,
READY,
DELIVERED;
}
-
自定义枚举方法
public class Pizza { private PizzaStatus status; public enum PizzaStatus { ORDERED, READY, DELIVERED; } public boolean isDeliverable() { if (getStatus() == PizzaStatus.READY) { return true; } return false; } // Methods that set and get the status variable. }
-
使用==比较枚举类型
由于枚举类型确保JVM中仅存在一个常量实例,因此我们可以安全地使用“ ==”运算符比较两个变量。此外,“ ==”运算符可提供编译时和运行时的安全性
以下代码段中的运行时安全性,其中“ ==”运算符用于比较状态,并且如果两个值均为null 都不会引发 NullPointerException。相反,如果使用equals方法,将抛出 NullPointerException
if(testPz.getStatus().equals(Pizza.PizzaStatus.DELIVERED)); if(testPz.getStatus() == Pizza.PizzaStatus.DELIVERED);
对于编译时安全性,两个不同枚举类型进行比较,使用equal方法比较结果确定为true,因为getStatus方法的枚举值与另一个类型枚举值一致,但逻辑上应该为false。这个问题可以使用==操作符避免。因为编译器会表示类型不兼容错误
if(testPz.getStatus().equals(TestColor.GREEN)); if(testPz.getStatus() == TestColor.GREEN);
-
在switch语句中使用枚举类型
public int getDeliveryTimeInDays() { switch (status) { case ORDERED: return 5; case READY: return 2; case DELIVERED: return 0; } return 0; }
-
枚举类型的属性,方法和构造函数
-
一旦为枚举类显式定义了带参数的构造器,则列出枚举值时也必须对应地传入参数
public enum Gender { MALE,FEMALE; // 定义一个public修饰的实例变量 public String name; } ----------------------------------- public class GenderTest { public static void main(String[] args) { // 通过Enum的valueOf()方法来获取指定枚举类的枚举值 Gender g = Enum.valueOf(Gender.class , "FEMALE"); // 直接为枚举值的name实例变量赋值 g.name = "女"; // 直接访问枚举值的name实例变量 System.out.println(g + "代表:" + g.name); } } ------------------------------------- public enum Gender { MALE,FEMALE; private String name; public void setName(String name) { switch (this) { case MALE: if (name.equals("男")) { this.name = name; } else { System.out.println("参数错误"); return; } break; case FEMALE: if (name.equals("女")) { this.name = name; } else { System.out.println("参数错误"); return; } break; } } public String getName() { return this.name; } } ------------------------------------------ public class GenderTest { public static void main(String[] args) { Gender g = Gender.valueOf("FEMALE"); g.setName("女"); System.out.println(g + "代表:" + g.getName()); // 此时设置name值时将会提示参数错误。 g.setName("男"); System.out.println(g + "代表:" + g.getName()); } } -------------------------------------------- public enum Gender { // 此处的枚举值必须调用对应构造器来创建 MALE("男"),FEMALE("女"); private final String name; // 枚举类的构造器只能使用private修饰 private Gender(String name) { this.name = name; } public String getName() { return this.name; } } -------------------------------------------------- public class GenderTest { public static void main(String[] args) { Gender g = Gender.valueOf("FEMALE"); Gender m = Gender.valueOf("MALE"); System.out.println(g + "代表:" + g.getName()); System.out.println(m + "代表:" + m.getName()); } }
-
-
EnumSet
与
HashSet
相比,由于使用了内部位向量表示,因此它是特定Enum
常量集的非常有效且紧凑的表示形式它提供了类型安全的替代方法,以替代传统的基于int的“位标志”,使我们能够编写更易读和易于维护的简洁代码
EnumSet
是抽象类,其有两个实现:RegularEnumSet
、JumboEnumSet
,选择哪一个取决于实例化时枚举中常量的数量 -
EnumSet
EnumMap
是一个专门化的映射实现,用于将枚举常量用作键。与对应的HashMap
相比,它是一个高效紧凑的实现,并且在内部表示为一个数组 -
通过枚举实现一些设计模式
-
单例模式
public enum Singleton { INSTANCE; public void doSomething() { System.out.println("doSomething"); } } ------------------------------------------ public class Main { public static void main(String[] args) { Singleton.INSTANCE.doSomething(); } }
-
策略模式
public enum StrategyEnum { ADD("+") { @Override public int exec(int a, int b) { return a+b; } }, SUB("-") { @Override public int exec(int a, int b) { return a-b; } }, MUTI("*") { @Override public int exec(int a, int b) { return a*b; } }; StrategyEnum(String value) { this.value = value; } private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public abstract int exec(int a, int b); } -------------------------------------------- public class StrategyEnumTest { public static void main(String[] args) { System.out.println( StrategyEnum.ADD.exec(2,4)); System.out.println( StrategyEnum.SUB.exec(2,4)); System.out.println( StrategyEnum.MUTI.exec(2,4)); } }
-
-
eg
public enum PinType { REGISTER(100000, "注册使用"), FORGET_PASSWORD(100001, "忘记密码使用"), UPDATE_PHONE_NUMBER(100002, "更新手机号码使用"); privatefinalint code; privatefinal String message; PinType(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public String getMessage() { return message; } @Override public String toString() { return"PinType{" + "code=" + code + ", message='" + message + '\'' + '}'; } }
System.out.println(PinType.FORGET_PASSWORD.getCode()); System.out.println(PinType.FORGET_PASSWORD.getMessage()); System.out.println(PinType.FORGET_PASSWORD.toString()); 100001 忘记密码使用 PinType{code=100001, message='忘记密码使用'}
public enum Day2 { MONDAY("星期一",1), TUESDAY("星期二",2), WEDNESDAY("星期三",3), THURSDAY("星期四",4), FRIDAY("星期五",5), SATURDAY("星期六",6), SUNDAY("星期日",7);//记住要用分号结束 private String desc;//文字描述 private Integer code; //对应的代码 /** * 私有构造,防止被外部调用 * @param desc */ private Day2(String desc,Integer code){ this.desc=desc; this.code=code; } /** * 定义方法,返回描述,跟常规类的定义没区别 * @return */ public String getDesc(){ return desc; } /** * 定义方法,返回代码,跟常规类的定义没区别 * @return */ public int getCode(){ return code; } public static void main(String[] args){ for (Day2 day:Day2.values()) { System.out.println("name:"+day.name()+ ",desc:"+day.getDesc()); } }
枚举与魔法值
-
魔法值概念:具体的数字和字符
常量在代码中具有穿透性,使用甚广。如果没有一个恰当的命名,就会给代码阅读带来沉重的负担,甚至影响对主干逻辑的理解。首当其冲的问题就是到处使用魔法值
魔法值即"共识层面"上的常量,直接以具体的数值或者字符出现在代码中。这些不知所云的魔法值极大地影响了代码的可读性和可维护性
-
解决方案
- 静态常量–static final 定义的
- 枚举类—枚举类适合所有常量已经确定,后期几乎固定不变,这样才符合枚举类的宗旨
-
魔法值避免
即使类内常量和局部常量当前只使用一次,也需要赋予一个有意义的名称,目的有两个:第一,望文知义,方便理解;第二,后期多次使用时能保证值出同源。因此,无论如何都不允许任何魔法值直接出现在代码中,避免魔法值随意使用导致取值不一致,特别是对于字符串常量来说,应该避免没有预先定义,就直接使用魔法值。
某些公认的字面常量是不需要预先定义的,如for(int i=;…)这里的0是可以直接使用的。true和false也可以直接使用,但是如果具备了特殊的含义,就必须定义出有意义的常量名称,比如在TreeMap源码中,表示红黑树节点颜色的true和false就被定义成为类内常量
eg:
public void getOnlinePackageCourse(Long packageId,Long userId){
if(packageId == 3){
logger.error("线下课程,无法在线观看");
return;
}
}
//其他逻辑处理
PackageCourse online = packageService.getByTeacherId(userId);
if(online.getPackageId() == 2){
logger.error("未审核课程");
return;
}
以上代码中,信手拈来的2和3分别表示未审核课程和线下课程,仅仅是两个数字,似乎很容易记忆。但事实上,除了2和3两种状态外,还有1、4、5分别代表新建、审核未通过、审核通过。在团队规模较小时,口口相传,倒也勉强能够记住这五个数字的含义,早期还有零星的注释,驾轻就熟的情况下,连注释也省了。现实是残酷的,团队迅速扩大后,课程状态个数也在逐步增加,新来的开发工程师在上线新功能模块时,把"审核通过"和"未审核课程"对应的数字搞反了,使得课程展示错误,导致用户大量投诉
String key = "Id#taobao_" + tradeId;
cache.put(key,value);
上述代码是保存信息到缓存中的方法,即使用魔法值组装Key。这就导致各个调用方导出复制和粘贴字符串Id#taobao_,这样似乎很合理。但某一天,某个粗心的程序员把Id#taobao_复制成了Id#taobao,少了下划线。这个错误在测试过程中,并不容易发现,因为没有命中缓存,会自动访问数据库。但在大促时,数据库压力急剧上升,进而发现缓存全部失效,导致连接占满,查询变慢