編寫高質量程式碼:改善Java程式的151個建議(第6章:列舉和註解___建議83~87)
列舉和註解都是在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<String> 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<String> 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<String> 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("無相關列舉項");
}
}
}
}
個人感覺第二種