2010-03-10 175 views
31

我們有一個REST API,其中客戶端可以提供表示Java Enums中服務器上定義的值的參數。查找Java枚舉的最佳實踐

所以我們可以提供一個描述性錯誤,我們將這個lookup方法添加到每個枚舉。好像我們只是複製代碼(壞)。有更好的做法嗎?

public enum MyEnum { 
    A, B, C, D; 

    public static MyEnum lookup(String id) { 
     try { 
      return MyEnum.valueOf(id); 
     } catch (IllegalArgumentException e) { 
      throw new RuntimeException("Invalid value for my enum blah blah: " + id); 
     } 
    } 
} 

更新:由valueOf(..)提供的默認錯誤信息會No enum const class a.b.c.MyEnum.BadValue。我想從API中提供更多的描述性錯誤。

+2

好吧,IllegalArgumentException沒有錯,它已經是一個RuntimeException。你想在這裏改進什麼? – Riduidel 2010-03-10 16:50:39

+0

添加比僅使用'valueOf'(和'IllegalArgumentException')時提供的描述性消息更多的描述性消息 – 2010-03-10 16:51:47

回答

26

也許你可以實現通用靜態lookup方法。

像這樣

public class LookupUtil { 
    public static <E extends Enum<E>> E lookup(Class<E> e, String id) { 
     try {   
     E result = Enum.valueOf(e, id); 
     } catch (IllegalArgumentException e) { 
     // log error or something here 

     throw new RuntimeException(
      "Invalid value for enum " + e.getSimpleName() + ": " + id); 
     } 

     return result; 
    } 
} 

然後你可以

public enum MyEnum { 
    static public MyEnum lookup(String id) { 
     return LookupUtil.lookup(MyEnum.class, id); 
    } 
} 

或顯式調用實用程序類查找方法。

+0

您不能創建基類枚舉來擴展子枚舉,因爲「所有枚舉隱式擴展java.lang.Enum。不支持多重繼承,枚舉不能擴展其他任何東西「,所以無處可放這個代碼,除非你想把它放在某種靜態工具類中,並用enum類參數調用它。 – 2010-03-10 16:55:45

+0

只是調用Enum.valueOf,你的實用方法有什麼優勢? – sleske 2010-03-10 17:15:36

+0

發佈和預先操作和錯誤處理。據我所知它是錯誤處理,它是複製和粘貼不同的枚舉類型。 – 2010-03-10 17:18:07

3

爲什麼我們要寫5行代碼?

public class EnumTest { 
public enum MyEnum { 
    A, B, C, D; 
} 

@Test 
public void test() throws Exception { 
    MyEnum.valueOf("A"); //gives you A 
    //this throws ILlegalargument without having to do any lookup 
    MyEnum.valueOf("RADD"); 
} 
} 
+0

這個想法/要求是重新拋出特殊的例外 – 2010-03-10 17:28:26

+0

沒錯。有關「默認」錯誤與更多描述性錯誤的更多描述,請參閱更新後的文章。 – 2010-03-17 15:49:01

+0

在這種情況下,來自@「Mykola Golubyev」的答案應該已經解決了您的靜態查找實用程序類的問題,並且您可以在catch塊中拋出任何想要的消息/異常? – 2010-03-17 16:30:57

3

如果您想查詢是不區分大小寫的,你可以通過做多一點友好的數值循環:

public enum MyEnum { 
    A, B, C, D; 

     public static MyEnum lookup(String id) { 
     boolean found = false; 
     for(MyEnum enum: values()){ 
      if(enum.toString().equalsIgnoreCase(id)) found = true; 
     } 
     if(!found) throw new RuntimeException("Invalid value for my enum: " +id); 
     } 
} 
+0

好的建議,不是我真正想要的,而是一個好主意。注意在你的'lookup'中,你實際上並沒有從方法中返回任何值。 – 2010-03-10 18:07:37

+0

好點。應該是: if(enum.toString()。equalsIgnoreCase(id))return enum; 然後,您可以消除布爾值,只要拋出異常就會拋出異常。 – Adam 2010-03-10 20:58:55

11

看起來你在這裏有一個不好的做法,但不是你想的。

抓住IllegalArgumentException用更清晰的消息重新拋出另一個RuntimeException可能看起來像一個好主意,但事實並非如此。因爲這意味着你關心異常中的消息。

如果您關心異常中的消息,那麼這意味着您的用戶在某種程度上看到了您的異常。這不好。

如果您想向用戶提供明確的錯誤消息,則應在解析用戶輸入時檢查枚舉值的有效性,並在用戶輸入錯誤時在響應中發送相應的錯誤消息。

喜歡的東西:

// This code uses pure fantasy, you are warned! 
class MyApi 
{ 
    // Return the 24-hour from a 12-hour and AM/PM 

    void getHour24(Request request, Response response) 
    { 
     // validate user input 
     int nTime12 = 1; 
     try 
     { 
      nTime12 = Integer.parseInt(request.getParam("hour12")); 
      if(nTime12 <= 0 || nTime12 > 12) 
      { 
       throw new NumberFormatException(); 
      } 
     } 
     catch(NumberFormatException e) 
     { 
      response.setCode(400); // Bad request 
      response.setContent("time12 must be an integer between 1 and 12"); 
      return; 
     } 

     AMPM pm = null; 
     try 
     { 
      pm = AMPM.lookup(request.getParam("pm")); 
     } 
     catch(IllegalArgumentException e) 
     { 
      response.setCode(400); // Bad request 
      response.setContent("pm must be one of " + AMPM.values()); 
      return; 
     } 

     response.setCode(200); 
     switch(pm) 
     { 
      case AM: 
       response.setContent(nTime12); 
       break; 
      case PM: 
       response.setContent(nTime12 + 12); 
       break; 
     } 
     return; 
    } 
} 
+0

我認爲我們實際上是按照你的建議去做的。我們拋出異常,然後棧上的一個類捕獲它,然後通過REST API返回異常消息給客戶端(在定義的錯誤響應中) – 2010-03-10 18:06:01

+5

我建議你不要捕獲泛型異常以返回它們消息,但返回該消息意在解釋爲什麼異常被捕獲。根據您的例外情況進行通用錯誤響應是內部架構泄露給客戶端,這是一種不好的做法。您的API的錯誤消息不應該依賴於實現的錯誤報告體系結構(在這種情況下,Java異常)。 – 2010-03-10 21:20:44

2

IllegalArgumentException異常的錯誤消息是已經有足夠的描述。

你的方法使用相同的消息只是重寫了一個特定的異常。開發人員更喜歡特定的異常類型,並且可以適當地處理這種情況,而不是嘗試處理RuntimeException。

如果意圖是使消息更加用戶友好,那麼對枚舉值的引用無論如何都與它們無關。讓UI代碼確定應向用戶顯示的內容,並且UI開發人員最好使用IllegalArgumentException。

+0

「默認」錯誤消息是:'沒有枚舉常量類a.b.c.MyEnum.BadValue'。我寧願從REST API返回更相關的錯誤消息。創建前端的開發人員不會真正知道如何處理默認錯誤,而不是僅僅顯示它 - 這是我想避免的。 – 2010-03-17 15:46:11

+2

我無法看到您的示例消息如何更好。對於用戶來說都不合適,所以應該處理異常並且顯示正確的面向用戶的消息。這是不同的,然後將消息放入異常中,這應該是開發者的。 – Robin 2010-03-17 18:45:27

3

更新:由於GreenTurtle正確地指出,下列哪項是錯誤


我只想寫

boolean result = Arrays.asList(FooEnum.values()).contains("Foo"); 

這可能是比捕捉運行時異常少高性能,反而使得對於更清潔代碼。捕捉這樣的例外總是一個壞主意,因爲它很容易誤診。 當檢索比較值本身會導致IllegalArgumentException時會發生什麼?然後這將被處理爲與枚舉器不匹配的值。

+6

除非在FooEnum類中實現了一個可以處理String對象的equals方法,否則此代碼將永遠不會返回true。發生這種情況是因爲生成的ArrayList包含FooEnum類型的對象,並且如果使用String對象請求包含,將始終返回false。 – GreenTurtle 2015-03-27 13:56:49

+0

@GreenTurtle是的,你顯然是對的,忘記了Enum#只能用於對象比較。對於枚舉,你甚至不應該能夠實現你自己的平等。 我仍然會盡量避免捕獲泛型運行時異常,只是遍歷它,做一個字符串比較。 – makrom 2015-10-24 17:03:49

0

當涉及到Rest/Json等時,我們會像這樣做所有的枚舉。 它的優點是錯誤是人類可讀的,並且還爲您提供可接受的值列表。我們使用自定義方法MyEnum.fromString而不是MyEnum.valueOf,希望它有幫助。

public enum MyEnum { 

    A, B, C, D; 

    private static final Map<String, MyEnum> NAME_MAP = Stream.of(values()) 
      .collect(Collectors.toMap(MyEnum::toString, Function.identity())); 

    public static MyEnum fromString(final String name) { 
     MyEnum myEnum = NAME_MAP.get(name); 
     if (null == myEnum) { 
      throw new IllegalArgumentException(String.format("'%s' has no corresponding value. Accepted values: %s", name, Arrays.asList(values()))); 
     } 
     return myEnum; 
    } 
} 

因此,例如,如果你調用

MyEnum value = MyEnum.fromString("X"); 

,你會得到一個IllegalArgumentException以下消息:

'X' 沒有相應的值。接受的值:[A,B,C,D]

您可以將IllegalArgumentException更改爲自定義一個。