首页 » 编写高质量代码:改善Java程序的151个建议 » 编写高质量代码:改善Java程序的151个建议全文在线阅读

《编写高质量代码:改善Java程序的151个建议》建议87:使用valueOf前必须进行校验

关灯直达底部

我们知道每个枚举都是java.lang.Enum的子类,都可以访问Enum类提供的方法,比如hashCode、name、valueOf等,其中valueOf方法会把一个String类型的名称转变为枚举项,也就是在枚举项中查找出字面值与该参数相等的枚举项。虽然这个方法很简单,但是JDK却做了一个对于开发人员来说并不简单的处理。我们来看代码:


public static void main(Stringargs){

//注意summer是小写

List<String>params=Arrays.asList("Spring","summer");

for(String name:params){

//查找字面值与name相同的枚举项

Season s=Season.valueOf(name);

if(s!=null){

//有该枚举项时

System.out.println(s);

}else{

//没有该枚举项

System.out.println("无相关枚举项");

}

}

}


这段程序看似很完美了,其中考虑到从String转换为枚举类型可能存在着转换不成功的情况,比如没有匹配到指定值,此时valueOf的返回值应该为空,所以后面又紧跟着if……else判断输出。这段程序真的完美无缺了吗?那我们看看运行结果:


Spring

Exception in thread"main"java.lang.IllegalArgumentException:No enum const class

Season.summer

at java.lang.Enum.valueOf(Enum.java:196)

at Season.valueOf(Client.java:1)

at Client.main(Client.java:13)


报无效参数异常,也就是说我们的summer(注意s小写)无法转换为Season枚举,无法转换就不转换嘛,那也别抛出非受检IllegalArgumentException异常啊,一旦抛出这个异常,后续的代码就不会运行了,这才是要命呀!这与我们的习惯用法非常不一致,例如我们从一个List中查找一个元素,即使不存在也不会报错,顶多indexOf方法返回-1。

那么来深入分析一下该问题,valueOf方法的源代码如下:


public static<T extends Enum<T>>T valueOf(Class<T>enumType,

String name){

//通过反射,从常量列表中查找

T result=enumType.enumConstantDirectory().get(name);

if(result!=null)

return result;

if(name==null)

throw new NullPointerException("Name is null");

//最后排除无效参数异常

throw new IllegalArgumentException("No enum const"+enumType+"."+name);

}


valueOf方法先通过反射从枚举类的常量声明中查找,若找到就直接返回,若找不到则抛出无效参数异常。valueOf本意是保护编码中的枚举安全性,使其不产生空枚举对象,简化枚举操作,但是却又引入了一个我们无法避免的IllegalArgumentException异常。

可能会有读者认为此处valueOf方法的源代码不对,这里要输入2个参数,而我们的Season.valueOf只传递一个String类型的参数。真的是这样吗?是的,因为valueOf(String name)方法是不可见的,是JVM内置的方法,我们只有通过阅读公开的valueOf方法来了解其运行原理了。

问题清楚了,有两个方法可以解决此问题:

(1)使用try…catch捕捉异常

这是最直接也是最简单的方式,产生IllegalArgumentException即可确认为没有相同名称的枚举项,代码如下:


try{

Season s=Season.valueOf(name);

//有该枚举项时的处理

System.out.println(s);

}catch(Exception e){

System.out.println("无相关枚举项");

}


(2)扩展枚举类

由于Enum类定义的方法基本上都是final类型的,所以不希望被覆写,那我们可以学习String和List,通过增加一个contains方法来判断是否包含指定的枚举项,然后再继续转换,代码如下:


enum Season{

Spring, Summer, Autumn, Winter;

//是否包含指定名称的枚举项

public static boolean contains(String name){

//所有的枚举值

Seasonseason=values();

//遍历查找

for(Season s:season){

if(s.name().equals(name)){

return true;

}

}

return false;

}

}


Season枚举具备了静态方法contains后,就可以在valueOf前判断一下是否包含指定的枚举名称了,若包含则可以通过valueOf转换为Season枚举,若不包含则不转换。