我们知道每个枚举都是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枚举,若不包含则不转换。