我們知道每個枚舉都是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枚舉,若不包含則不轉換。