《編寫高質量代碼:改善 Java 程序的 151 個建議》第 6 章聚焦枚舉與注解。本文提煉建議 83-87 核心內容,結合實戰案例深度解析:為何枚舉優於常量接口、如何用構造器豐富枚舉語義、Switch 空指針陷阱、Default 防禦性編程、valueOf 異常處理模式。
enum Season { Spring, Summer, Autumn, Winter } 即完成定義;常量接口需逐個 int SPRING = 0。void doSomething(Season s) 編譯期保證只能傳入四個合法值;int 常量任意整數皆可傳入,需運行期校驗。values() 遍歷、valueOf(name) 反查、ordinal() 排序、compareTo() 比較。// 枚舉版:優雅、類型安全
enum Season { Spring, Summer, Autumn, Winter;
public static Season getComfortable() { return Spring; }
}
Season best = Season.getComfortable(); // 直接調用
// 常量接口版:需 switch/if 映射字面量
interface Season { int SPRING=0, SUMMER=1, AUTUMN=2, WINTER=3; }
int bestCode = Season.SPRING; // 只能拿編碼,無法直接輸出 "Spring"
枚舉不可繼承(隱式 final),常量接口可通過繼承擴展。但常量通常在項目初期定型,極少需動態擴展,故影響微乎其微。
為枚舉項附加業務屬性(中文名、顏色、權限范圍、生命周期等),構建「自描述」常量體系:
enum Role {
ADMIN("管理員", new LifeTime(365), new Scope("ALL")),
USER("普通用戶", new LifeTime(30), new Scope("READ"));
private final String displayName;
private final LifeTime lifeTime;
private final Scope scope;
Role(String displayName, LifeTime lt, Scope sc) {
this.displayName = displayName; this.lifeTime = lt; this.scope = sc;
}
// getter 省略
}
優勢:編譯期強制每個項提供完整屬性,避免漏配;IDE 自動提示屬性訪問;多維度信息集中維護,比分散的註釋可靠得多。
枚舉 Switch 底層編譯為 switch (enum.ordinal()),若傳入 null 直接拋 NPE:
public void doSports(Season season) {
switch (season) { // season 為 null 時這裡拋 NPE
case Spring: ... break;
default: System.out.println("錯誤");
}
}
doSports(null); // NullPointerException at switch line
public void doSports(Season season) {
if (season == null) { throw new IllegalArgumentException("Season 不能為空"); }
switch (season) { ... }
}
當枚舉新增項但忘記更新 Switch 時,Default 兜底能讓錯誤即刻暴露:
enum LogLevel { DEBUG, INFO, WARN, ERROR, FATAL } // 新增 FATAL
switch (level) {
case DEBUG: ... break;
case INFO: ... break;
case WARN: ... break;
case ERROR: ... break;
default: throw new AssertionError("未處理的日志級別: " + level); // 運行期立即報錯
}
輔助手段:IDE 設置 Enum type constant not covered on 'switch' = Error,編譯期直接報錯。
Enum.valueOf(String) 找不到匹配項拋 IllegalArgumentException,而非返回 null,導致調用鏈中斷:
List<String> inputs = Arrays.asList("Spring", "summer"); // 注意大小寫
for (String s : inputs) {
Season season = Season.valueOf(s); // "summer" 拋異常,後續不執行
System.out.println(season);
}
IllegalArgumentException 視為無效。enum Season {
Spring, Summer, Autumn, Winter;
public static boolean contains(String name) {
for (Season s : values()) if (s.name().equals(name)) return true;
return false;
}
}
// 使用
if (Season.contains(input)) { Season s = Season.valueOf(input); ... }
第二種模式語義清晰、性能可接受(枚舉項通常<10個),且避免異常控制流開銷。
枚舉是 Java 類型系統的強大補充,善用其「類+實例」雙重身份,能將「魔術常量」轉化為「領域模型」,實現編譯期約束、運行期多態與自文檔化。注解作為元數據載體,配合 APT/編譯期處理,更是框架設計的基石。牢記以上 5 條建議,代碼質量將顯著提升。