Top

2019年4月30日 星期二

程式設計|JAVA|Enum 用 Name 取得而不拋出 Exception 的最佳方法介紹

  一般初學 JAVA 的人在把 String 轉成 Enum 的時候最長會直接使用 Week.valueOf("Monday") 來轉,這時候你就會遇到一個問題:要是放入的字串不在 Enum 宣告裡面的時候,他可是會拋出 IllegalArgumentException 的報錯。

  而 Enum 這個結構是靜態資源,它在編譯時期就被固定了,所以無法在 Runtime 時期做動態新增、修改。這邊因為會報錯,所以很多人就會直接用 try catch 去把報錯攔截住,請不要這麼懶惰好嗎?讓我們用正確的方法來解決它!😎

  基於要用 String 取得 Enum 又不報錯的原則,也有許多人用了下面的方法:

public static Week iterationFindByName(String day) {
    for (Week week: Week.values()) {
        if (week.name().equals(day)) {
            return week;
        }
    }
    return null;
}

  沒錯,用迴圈跑一遍整個 Enum 比對一次就可以取得 String 對應的 Enum 了,然後沒找到就回傳 null 。
  的確,這個寫法不會拋 Exception ,但是這時就會有效能問題,你每次找都要跑一次迴圈,這個複雜度是 O(n) ,也就是最差情況有N個 Enum ,你的迴圈就跑比對 N 次。
  或許會有人辯解說:反正一般 Enum 也不會太多個,這點 cast 還好吧? 對,一般情況是這樣,但也許哪天你遇到真的連這點 cast 也要省的情況,你不會希望為了這個來 review 你的程式碼吧?

  下面開始介紹幾個較好的解法:
一、使用靜態 Map 封裝

private static final Map<String, Week> DAY_INDEX = Maps.newHashMapWithExpectedSize(Week.values().length);

static {
    for (Week week: Week.values()) {
        DAY_INDEX.put(week.name(), week);
    }
}

public static Week iterationFindByName(String day) {
    return DAY_INDEX.get(day);
}

  這個方法就比較有智慧,你只需要花點記憶體,就可以把原來的複雜度從 O(n) 減到最低的 O(1)。

二、使用 Guava Enums.getIfPresent (推薦)

public static Week iterationFindByName(String day) {
    return Enums.getIfPresent(Week.class, day).orNull();
}

  在這個套件裡面使用 WeakReferences 和 WeakHashMaps,基本上它會建立一個全域的靜態map,一樣使用 Enum 的 name 當鍵值來尋找 Enum。順便一提,這也是 Google 工程師們最常來解決這件事的方法😁

  當然,你不想回傳 null 也可以,只要把 orNull() 換成 or(UNKNOWN) 就好,UNKNOWN 是自己另外定義的 Enum。

沒有留言:

張貼留言