Java枚举反向查找最佳实践

发布于 2024-10-22 06:03:36 字数 1208 浏览 2 评论 0原文

我在博客上看到建议以下内容是在 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;
    }
}

这两种方法都存在明显的问题吗?是否有推荐的方法来实现这种查找?

I saw it suggested on a blog that the following was a reasonable way to do a "reverse-lookup" using the getCode(int) in a Java enum:

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); 
    }
}

To me, the static map and the static initializer both look like a bad idea, and my first thought would be to code the lookup as so:

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;
    }
}

Are there any obvious problems with either method, and is there a recommended way to implement this kind of lookup?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

ら栖息 2024-10-29 06:03:36

Maps.uniqueIndex 来自 Google 的 Guava 对于构建查找地图非常方便。

更新:以下是在 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);
    }
}

Maps.uniqueIndex from Google's Guava is quite handy for building lookup maps.

Update: Here is an example using Maps.uniqueIndex with Java 8:

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);
    }
}
一曲琵琶半遮面シ 2024-10-29 06:03:36

虽然静态映射的开销较高,但它还是不错的,因为它通过code提供恒定时间查找。您的实现的查找时间随着枚举中元素的数量线性增加。对于小型枚举,这根本不会有太大贡献。

这两种实现(也可以说是一般的 Java 枚举)的一个问题是 Status 确实可以采用一个隐藏的额外值:null。根据业务逻辑的规则,当查找“失败”时,返回实际的枚举值或抛出Exception可能是有意义的。

Though it has higher overhead, the static map is nice because it offers constant-time lookup by code. Your implementation's lookup time increases linearly with the number of elements in the enum. For small enums, this simply will not contribute significantly.

One issue with both implementations (and, arguably, with Java enums in general) is that there's really a hidden extra value that a Status can take on: null. Depending on the rules of the business logic, it may make sense to return an actual enum value, or throw an Exception, when the lookup "fails."

执手闯天涯 2024-10-29 06:03:36

这是一个可能更快一点的替代方案:

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;
    }
}

当然,如果您希望以后能够添加更多常量,那么这并不是真正可维护的。

Here is an alternative which may be even a bit faster:

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;
    }
}

Of course, this is not really maintainable if you want to be able to add more constants later.

过去的过去 2024-10-29 06:03:36

显然,映射将提供恒定时间查找,而循环则不会。在具有很少值的典型枚举中,我没有发现遍历查找有问题。

Obviously the map will provide constant time lookup whereas the loop won't. In a typical enum with few values, I don't see a problem with the traversal lookup.

暮光沉寂 2024-10-29 06:03:36

这是 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");

    }
}

Here is an Java 8 alternative (with unit test):

// 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");

    }
}
城歌 2024-10-29 06:03:36
@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);
    }
}
@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);
    }
}
浅浅 2024-10-29 06:03:36

在 Java 8 中,我只需将以下工厂方法添加到枚举中并跳过查找映射。

public static Optional<Status> of(int value) {
    return Arrays.stream(values()).filter(v -> value == v.getCode()).findFirst();
}

In Java 8 I would just add the following factory method to your enum and skip the lookup Map.

public static Optional<Status> of(int value) {
    return Arrays.stream(values()).filter(v -> value == v.getCode()).findFirst();
}
向日葵 2024-10-29 06:03:36

两种方式都是完全有效的。从技术上讲,它们的运行时间是相同的。

但是,如果您首先将所有值保存到 Map,则可以节省每次要进行查找时迭代该集合所需的时间。所以,我认为静态映射和初始化程序是一个稍微更好的方法。

Both ways are perfectly valid. And they have technically the same Big-Oh running time.

However, if you save all of the values to a Map first, you save the time it takes to iterate through the set each time you want to do a lookup. So, I think that the static map and initializer are a slightly better way to go.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文