查找Java枚举的最佳实践
我们有一个RESTAPI,客户机可以在其中提供表示Java枚举中服务器上定义的值的参数 因此,我们可以提供一个描述性错误,我们将这个查找Java枚举的最佳实践,java,enums,Java,Enums,我们有一个RESTAPI,客户机可以在其中提供表示Java枚举中服务器上定义的值的参数 因此,我们可以提供一个描述性错误,我们将这个查找方法添加到每个枚举中。看起来我们只是在复制代码(糟糕)。有更好的做法吗 public enum MyEnum { A, B, C, D; public static MyEnum lookup(String id) { try { return MyEnum.valueOf(id); }
查找
方法添加到每个枚举中。看起来我们只是在复制代码(糟糕)。有更好的做法吗
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中提供一个更具描述性的错误 您可能可以实现通用的静态查找方法
像这样
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;
}
}
或者显式调用实用程序类查找方法。为什么我们必须编写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");
}
}
如果希望查找不区分大小写,可以通过值进行循环,使其更友好:
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);
}
}
看起来你在这里练习得不好,但不是在你想的地方
捕获一个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;
}
}
//此代码使用纯幻想,警告您!
类MyApi
{
//从12小时和上午/下午返回24小时
void getHour24(请求、响应)
{
//验证用户输入
int nTime12=1;
尝试
{
nTime12=Integer.parseInt(request.getParam(“hour12”);
如果(nTime12)
{
抛出新的NumberFormatException();
}
}
捕获(数字格式)
{
response.setCode(400);//请求错误
response.setContent(“time12必须是介于1和12之间的整数”);
返回;
}
AMPM-pm=null;
尝试
{
pm=AMPM.lookup(request.getParam(“pm”);
}
捕获(IllegalArgumentException e)
{
response.setCode(400);//请求错误
response.setContent(“pm必须是”+AMPM.values()之一);
返回;
}
响应。设置代码(200);
开关(pm)
{
案例AM:
响应。设置内容(nTime12);
打破
个案下午:
响应。设置内容(nTime12+12);
打破
}
返回;
}
}
IllegalArgumentException中的错误消息已经足够描述性了
您的方法对一个特定的异常生成一个通用异常,并简单地重写相同的消息。开发人员更喜欢特定的异常类型,并且可以适当地处理该情况,而不是试图处理RuntimeException
如果目的是使消息更加用户友好,那么对枚举值的引用与它们无关。让UI代码决定应该向用户显示什么,UI开发人员最好使用IllegalArgumentException。更新:正如Greentourt正确指出的,以下是错误的
我只会写信
boolean result = Arrays.asList(FooEnum.values()).contains("Foo");
这可能比捕获运行时异常的性能要差,但会使代码更干净。捕捉这样的异常总是一个坏主意,因为它容易被误诊。
当检索比较值本身导致IllegalArgumentException时会发生什么情况?然后,这将被视为枚举数的不匹配值。对于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;
}
}
您将收到带有以下消息的IllegalArgumentException:
“X”没有相应的值。接受值:[A、B、C、D]
您可以将IllegalArgumentException更改为自定义异常。Guava还提供了这样的函数,如果找不到枚举,该函数将返回一个可选的
Enums.getIfPresent(MyEnum.class, id).toJavaUtil()
.orElseThrow(()-> new RuntimeException("Invalid enum blah blah blah.....")))
您可以使用静态查找映射来避免异常并返回null,然后根据需要抛出:
public enum Mammal {
COW,
MOUSE,
OPOSSUM;
private static Map<String, Mammal> lookup =
Arrays.stream(values())
.collect(Collectors.toMap(Enum::name, Function.identity()));
public static Mammal getByName(String name) {
return lookup.get(name);
}
}
公共枚举哺乳动物{
牛,
老鼠
负鼠;
专用静态映射查找=
Arrays.stream(values())
.collect(Collectors.toMap(Enum::name,Function.identity());
公共静态哺乳动物getByName(字符串名称){
返回lookup.get(name);
}
}
Apache Commons Lang 3包含类EnumUtils。如果您在项目中没有使用ApacheCommons,那么您就错了。你在重新发明轮子
我们可以毫无例外地使用十几种很酷的方法。例如:
获取类的枚举,如果找不到,则返回null
此方法与Enum.valueOf的不同之处在于它不会引发
Exception用于无效的枚举名称,并执行不区分大小写的操作
名称的匹配
嗯,IllegalArgumentException没有什么问题,它已经是一个RuntimeException了。您想在这里改进什么?添加一条比仅使用valueOf
(和IllegalArgumentException
)时提供的更具描述性的消息。您不能创建一个基本枚举来扩展子枚举,因为“所有枚举隐式扩展java.lang.enum。由于java不支持多重继承,枚举不能扩展任何其他内容”,所以没有地方可以放这段代码,除非你想把它放在某种静态实用程序类中,并用enum class参数调用它
Enums.getIfPresent(MyEnum.class, id).toJavaUtil()
.orElseThrow(()-> new RuntimeException("Invalid enum blah blah blah.....")))
public enum Mammal {
COW,
MOUSE,
OPOSSUM;
private static Map<String, Mammal> lookup =
Arrays.stream(values())
.collect(Collectors.toMap(Enum::name, Function.identity()));
public static Mammal getByName(String name) {
return lookup.get(name);
}
}
EnumUtils.getEnumIgnoreCase(SeasonEnum.class, season);