讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議87:使用valueOf前必須進行校驗 >

建議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枚舉,若不包含則不轉換。