[英]Java enum reverse look-up best practice
我在博客上看到它建议以下是使用 Java 枚举中的getCode(int)
进行“反向查找”的合理方法:
public enum Status {
WAITING(0),
READY(1),
SKIPPED(-1),
COMPLETED(5);
private static final Map<Integer,Status> lookup
= new HashMap<Integer,Status>();
static {
for(Status s : EnumSet.allOf(Status.class))
lookup.put(s.getCode(), s);
}
private int code;
private Status(int code) {
this.code = code;
}
public int getCode() { return code; }
public static Status get(int code) {
return lookup.get(code);
}
}
对我来说,静态映射和静态初始值设定项看起来都是个坏主意,我的第一个想法是将查找编码为:
public enum Status {
WAITING(0),
READY(1),
SKIPPED(-1),
COMPLETED(5);
private int code;
private Status(int code) {
this.code = code;
}
public int getCode() { return code; }
public static Status get(int code) {
for(Status s : values()) {
if(s.code == code) return s;
}
return null;
}
}
这两种方法是否有任何明显的问题,是否有推荐的方法来实现这种查找?
来自 Google Guava 的Maps.uniqueIndex对于构建查找地图非常方便。
更新:这是一个在 Java 8 中使用Maps.uniqueIndex
的示例:
public enum MyEnum {
A(0), B(1), C(2);
private static final Map<Integer, MyEnum> LOOKUP = Maps.uniqueIndex(
Arrays.asList(MyEnum.values()),
MyEnum::getStatus
);
private final int status;
MyEnum(int status) {
this.status = status;
}
public int getStatus() {
return status;
}
@Nullable
public static MyEnum fromStatus(int status) {
return LOOKUP.get(status);
}
}
虽然它有更高的开销,但静态地图很好,因为它提供了通过code
恒定时间查找。 您的实现的查找时间随着枚举中元素的数量线性增加。 对于小型枚举,这根本不会有显着贡献。
这两种实现(以及可以说是一般的 Java 枚举)的一个问题是Status
确实有一个隐藏的额外值: null
。 根据业务逻辑的规则,当查找“失败”时,返回一个实际的枚举值或抛出一个Exception
可能是有意义的。
这是一个可能更快的替代方案:
public enum Status {
WAITING(0),
READY(1),
SKIPPED(-1),
COMPLETED(5);
private int code;
private Status(int code) {
this.code = code;
}
public int getCode() { return code; }
public static Status get(int code) {
switch(code) {
case 0: return WAITING;
case 1: return READY;
case -1: return SKIPPED;
case 5: return COMPLETED;
}
return null;
}
}
当然,如果您希望以后能够添加更多常量,这并不是真正可维护的。
显然,地图将提供恒定的时间查找,而循环则不会。 在一个典型的值很少的枚举中,我看不到遍历查找的问题。
这是一个 Java 8 替代方案(带单元测试):
// DictionarySupport.java :
import org.apache.commons.collections4.Factory;
import org.apache.commons.collections4.map.LazyMap;
import java.util.HashMap;
import java.util.Map;
public interface DictionarySupport<T extends Enum<T>> {
@SuppressWarnings("unchecked")
Map<Class<?>, Map<String, Object>> byCodeMap = LazyMap.lazyMap(new HashMap(), (Factory) HashMap::new);
@SuppressWarnings("unchecked")
Map<Class<?>, Map<Object, String>> byEnumMap = LazyMap.lazyMap(new HashMap(), (Factory) HashMap::new);
default void init(String code) {
byCodeMap.get(this.getClass()).put(code, this);
byEnumMap.get(this.getClass()).put(this, code) ;
}
static <T extends Enum<T>> T getByCode(Class<T> clazz, String code) {
clazz.getEnumConstants();
return (T) byCodeMap.get(clazz).get(code);
}
default <T extends Enum<T>> String getCode() {
return byEnumMap.get(this.getClass()).get(this);
}
}
// Dictionary 1:
public enum Dictionary1 implements DictionarySupport<Dictionary1> {
VALUE1("code1"),
VALUE2("code2");
private Dictionary1(String code) {
init(code);
}
}
// Dictionary 2:
public enum Dictionary2 implements DictionarySupport<Dictionary2> {
VALUE1("code1"),
VALUE2("code2");
private Dictionary2(String code) {
init(code);
}
}
// DictionarySupportTest.java:
import org.testng.annotations.Test;
import static org.fest.assertions.api.Assertions.assertThat;
public class DictionarySupportTest {
@Test
public void teetSlownikSupport() {
assertThat(getByCode(Dictionary1.class, "code1")).isEqualTo(Dictionary1.VALUE1);
assertThat(Dictionary1.VALUE1.getCode()).isEqualTo("code1");
assertThat(getByCode(Dictionary1.class, "code2")).isEqualTo(Dictionary1.VALUE2);
assertThat(Dictionary1.VALUE2.getCode()).isEqualTo("code2");
assertThat(getByCode(Dictionary2.class, "code1")).isEqualTo(Dictionary2.VALUE1);
assertThat(Dictionary2.VALUE1.getCode()).isEqualTo("code1");
assertThat(getByCode(Dictionary2.class, "code2")).isEqualTo(Dictionary2.VALUE2);
assertThat(Dictionary2.VALUE2.getCode()).isEqualTo("code2");
}
}
在 Java 8 中,我只需将以下工厂方法添加到您的枚举中并跳过查找映射。
public static Optional<Status> of(int value) {
return Arrays.stream(values()).filter(v -> value == v.getCode()).findFirst();
}
@AllArgsConstructor
@Getter
public enum MyEnum {
A(0),
B(1),
C(2);
private static final Map<Integer, MyEnum> LOOKUP =
Arrays.stream(MyEnum.values()).collect(Collectors.toMap(MyEnum::getStatus, Function.identity()));
private final int status;
@Nullable
public static MyEnum fromStatus(int status) {
return LOOKUP.get(status);
}
}
这两种方式都是完全有效的。 从技术上讲,它们具有相同的 Big-Oh 运行时间。
但是,如果您先将所有值保存到 Map 中,则可以节省每次要执行查找时遍历集合所需的时间。 所以,我认为静态映射和初始化器是一个稍微好一点的方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.