枚举和注解都是在Java1.5中引入的,枚举改变了常量的声明方式,注解耦合了数据和代码。
建议83:推荐使用枚举定义常量
常量声明是每一个项目都不可或缺的,在Java1.5之前,我们只有两种方式的声明:类常量和接口常量。不过,在1.5版本以后有了改进,即新增了一种常量声明方式:枚举声明常量,看如下代码:
enum Season {
SPRING, SUMMER, AUTUMN, WINTER;
}
提倡枚举项全部大写,字母之间用下划线分割,这也是从常量的角度考虑的。
那么枚举常量与我们经常使用的类常量和静态常量相比有什么优势?问得好,枚举的优点主要表现在四个方面:
1、枚举常量简单
2、枚举常量属于稳态型
public void describe(int s) {
// s变量不能超越边界,校验条件
if (s >= 0 && s < 4) {
switch (s) {
case Season.SPRING:
System.out.println("this is spring");
break;
case Season.SUMMER:
System.out.println("this is summer");
break;
......
}
}
}
对输入值的检查很吃力。
public void describe(Season s){
switch(s){
case Spring:
System.out.println("this is "+Season.Spring);
break;
case Summer:
System.out.println("this is summer"+Season.Summer);
break;
......
}
}
不用校验,已经限定了是Season枚举。
3、枚举具有内置方法
public void query() {
for (Season s : Season.values()) {
System.out.println(s);
}
}
通过values方法获得所有的枚举项。
4、枚举可以自定义方法
关键是枚举常量不仅可以定义静态方法,还可以定义非静态方法。
虽然枚举在很多方面比接口常量和类常量好用,但是有一点它是比不上接口常量和类常量的,那就是继承,枚举类型是不能继承的,也就是说一个枚举常量定义完毕后,除非修改重构,否则无法做扩展,而接口常量和类常量则可以通过继承进行扩展。但是,一般常量在项目构建时就定义完毕了,很少会出现必须通过扩展才能实现业务逻辑的场景。
注意:在项目中推荐使用枚举常量代替接口常量或类常量
建议84:使用构造函数协助描述枚举项
枚举描述:通过枚举的构造函数,声明每个枚举项必须具有的属性和行为,这是对枚举项的描述和补充。
enum Role {
Admin("管理员", new LifeTime(), new Scope()), User("普通用户", new LifeTime(), new Scope());
private String name;
private LifeTime lifeTime;
private Scope scope;
/* setter和getter方法略 */
Role(String _name, LifeTime _lifeTime, Scope _scope) {
name = _name;
lifeTime = _lifeTime;
scope = _scope;
}
}
class LifeTime {
}
class Scope {
}
这是一个角色定义类,描述了两个角色:管理员和普通用户,同时它还通过构造函数对这两个角色进行了描述:
1、name:表示的是该角色的中文名称
2、lifeTime:表示的是该角色的生命周期,也就是多长时间该角色失效
3、scope:表示的该角色的权限范围
这样一个描述可以使开发者对Admin和User两个常量有一个立体多维度的认知,有名称,有周期,还有范围,而且还可以在程序中方便的获得此类属性。所以,推荐大家在枚举定义中为每个枚举项定义描述,特别是在大规模的项目开发中,大量的常量定义使用枚举项描述比在接口常量或类常量中增加注释的方式友好的多,简洁的多。
建议85:小心switch带来的空指针异常
使用枚举定义常量时。会伴有大量switch语句判断,目的是为了每个枚举项解释其行为,例如这样一个方法:
public static void doSports(Season season) {
switch (season) {
case Spring:
System.out.println("春天放风筝");
break;
case Summer:
System.out.println("夏天游泳");
break;
case Autumn:
System.out.println("秋天是收获的季节");
break;
case Winter:
System.out.println("冬天滑冰");
break;
default:
System.out.println("输出错误");
break;
}
}
public static void main(String[] args) {
doSports(null);
}
Exception in thread "main" java.lang.NullPointerException
at com.book.study85.Client85.doSports(Client85.java:8)
at com.book.study85.Client85.main(Client85.java:28)
输入null时应该default的啊,为什么空指针异常呢?
目前Java中的switch语句只能判断byte、short、char、int类型(JDk7允许使用String类型),这是Java编译器的限制。问题是为什么枚举类型也可以跟在switch后面呢?
因为编译时,编译器判断出switch语句后跟的参数是枚举类型,然后就会根据枚举的排序值继续匹配,也就是或上面的代码与以下代码相同:
public static void doSports(Season season) {
switch (season.ordinal()) {//枚举的排序值
case season.Spring.ordinal():
System.out.println("春天放风筝");
break;
case season.Summer.ordinal():
System.out.println("夏天游泳");
break;
//......
}
}
switch语句是先计算season变量的排序值,然后与枚举常量的每个排序值进行对比,在我们的例子中season是null,无法执行ordinal()方法,于是就报空指针异常了。问题清楚了,解决很简单,在doSports方法中判断输入参数是否为null即可。
建议86:在switch的default代码块中增加AssertError错误
不懂
建议87:使用valueOf前必须进行校验
我们知道每个枚举项都是java.lang.Enum的子类,都可以访问Enum类提供的方法,比如hashCode、name、valueOf等,其中valueOf方法会把一个String类型的名称转换为枚举项,也就是在枚举项中查找出字面值与参数相等的枚举项。虽然这个方法简单,但是JDK却做了一个对于开发人员来说并不简单的处理,我们来看代码:
package OSChina.Client;
import java.util.Arrays;
import java.util.List;
public class Client15 {
enum Season{
SPRING,SUMMER,AUTUMN,WINTER
}
public static void main(String[] args) {
List params = Arrays.asList("SPRING","summer");
for (String name:params){
Season s = Season.valueOf(name);
if(null!=s){
System.out.println(s);
}else {
System.out.println("无相关枚举项");
}
}
}
}
看着没问题啊,summer不在Season里,就输出无相关枚举项就完事了嘛。。。
valueOf方法先通过反射从枚举类的常量声明中查找,若找到就直接返回,若找不到排除无效参数异常。valueOf本意是保护编码中的枚举安全性,使其不产生空枚举对象,简化枚举操作,但却引入了一个无法避免的IllegalArgumentException异常。
解决此问题的方法:
1、抛异常
package OSChina.Client;
import java.util.Arrays;
import java.util.List;
public class Client15 {
enum Season{
SPRING,SUMMER,AUTUMN,WINTER
}
public static void main(String[] args) {
List params = Arrays.asList("SPRING","summer");
try{
for (String name:params){
Season s = Season.valueOf(name);
System.out.println(s);
}
}catch (IllegalArgumentException e){
e.printStackTrace();
System.out.println("无相关枚举项");
}
}
}
2、扩展枚举类:
枚举中是可以定义方法的,那就在枚举项中自定义一个contains方法就可以。
package OSChina.Client;
import java.util.Arrays;
import java.util.List;
public class Client15 {
enum Season{
SPRING,SUMMER,AUTUMN,WINTER;
public static boolean contains(String name){
for (Season s:Season.values()){
if(s.name().equals(name)){
return true;
}
}
return false;
}
}
public static void main(String[] args) {
List params = Arrays.asList("SPRING","summer");
for (String name:params){
if(Season.contains(name)){
Season s = Season.valueOf(name);
System.out.println(s);
}else {
System.out.println("无相关枚举项");
}
}
}
}
个人感觉第二种方法更好一些!