1 背景
實際開發中根據枚舉的某個屬性擷取枚舉值非常常見。
如定義一個枚舉:
@Getter
public enum CoinEnum {
PENNY(1), NICKEL(5), DIME(10), QUARTER(25);
CoinEnum(int value) {
this.value = value;
}
private final int value;
}

實際開發中經常需要根據 value 的值來擷取枚舉對象。
那麼該怎麼做呢?
2 編碼
2.1 很low的寫法
工作中會見到有采用 switch-case 或者 if-else 實作根據某個屬性擷取枚舉的方式。
類似下面的這種寫法:
public static CoinEnum getEnum(int value) {
switch (value) {
case 1:
return PENNY;
case 5:
return NICKEL;
case 10:
return DIME;
case 25:
return QUARTER;
default:
return null;
}
}
這種寫法最大的問題是如果枚舉常量新增、删除、修改等都需要對該函數進行對應的修改,耦合非常高。
不符合開閉原則(開閉原則:對拓展開放,對修改關閉)。
另外如果枚舉常量較多,很容易映射錯誤,後期很難維護。
2.2 改進
我們可以采用枚舉類的 values 靜态函數擷取枚舉數組進行比對,寫出一個改進版本的代碼:
public static CoinEnum getEnum(int value) {
for (CoinEnum coinEnum : CoinEnum.values()) {
if (coinEnum.value == value) {
return coinEnum;
}
}
return null;
}
通過這種改進,後面需要對枚舉常量進行修改,該函數不需要改動,顯然比之前好了很多。
這種寫法在工作中也很常見。
那麼是否還有改進空間呢?
這種寫法雖然挺不錯,但是每次擷取枚舉對象都要周遊一次枚舉數組,時間複雜度是O(n)。
降低時間複雜度該怎麼做?一個常見的思路就是空間換時間。
2.3 再次優化
是以可以先用map緩存,使用時直接從map中取值。
可以這麼優化:
@Getter
public enum CoinEnum {
PENNY(1), NICKEL(5), DIME(10), QUARTER(25);
CoinEnum(int value) {
this.value = value;
}
private final int value;
public int value() {
return value;
}
private static final Map<Integer, CoinEnum> cache = new HashMap<>();
static {
for (CoinEnum coinEnum : CoinEnum.values()) {
cache.put(coinEnum.getValue(), coinEnum);
}
}
public static CoinEnum getEnum(int value) {
return cache.getOrDefault(value, null);
}
}
通過上面的優化,使用時時間複雜度為 O(1),性能有所提升。
實際開發中能采用這種寫法的同學都不太多。
主要原因是網上類似的文章不多,這也是很多總愛百度解決問題而不是思考來解決問題的同學進步不大的重要原因。
2.4 學無止境
通過上面兩次優化,代碼的耦合降低了,性能提高了。
是以,可以完美收工了?
NO...
2.3 給出的代碼還存在一些問題:
- 每個枚舉類中都需要編寫類似的代碼,很繁瑣。
- 引入提供上述工具的很多枚舉類,如果僅使用枚舉常量,也會觸發靜态代碼塊的執行。
還有沒有更優雅的方案呢?
希望大家可以思考一下,給出自己的解決方案。
具體解決方案參見
《阿裡巴巴Java開發手冊》詳解專欄的第 10 節 枚舉類的正确學習方式3 總結
既然選擇程式設計這條路,希望大家在提問之前,在百度之前,一定要先有自己的思考。
校招和社招時,很多大公司的面經非常吸引人,但是對大多數人幫助不大。是因為很多面經隻有題目沒有答案,好多人會搜尋各種答案來背誦,然而很多所謂的标準答案都沒有揭露問題的本質,都是不完整的,也不是最佳答案。
盡信書不如無書,希望大家在讀書、看部落格、看專欄等過程中更重視對問題的思考,對方法的學習,而是記憶具體知識點。俗話說“授人以魚不如授人以漁”,希望本文能夠啟發更多的朋友意識到思考和方法的重要性。
----------
如果你覺得本文對你有幫助,歡迎點贊、評論、轉發(注明出處)、關注,你的支援是我創作的最大動力。