78

ブログgetCode(int)で、Java 列挙型で を使用して「逆引き」を行うには、次の方法が合理的であることが示唆されているのを見ました。

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

どちらの方法にも明らかな問題はありますか? また、この種のルックアップを実装するための推奨される方法はありますか?

4

8 に答える 8

31

Google のGuavaのMaps.uniqueIndexは、ルックアップ マップの作成に非常に便利です。

Maps.uniqueIndex更新: 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);
    }
}
于 2011-03-15T18:33:07.947 に答える
20

オーバーヘッドは高くなりますが、スタティック マップは による一定時間のルックアップを提供するので便利ですcode。実装のルックアップ時間は、列挙型の要素数に比例して増加します。小さな列挙型の場合、これは単純に大きな貢献にはなりません。

両方の実装 (そしておそらく一般的な Java 列挙型) の 1 つの問題は、 a が取ることができる隠れた余分な値が実際に存在するStatusことです: null. ビジネス ロジックのルールによってはException、ルックアップが「失敗」したときに、実際の列挙値を返すか、 をスローすることが理にかなっている場合があります。

于 2011-03-15T18:34:06.940 に答える
7

これは、もう少し高速な代替手段です。

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

もちろん、後で定数を追加できるようにしたい場合、これは実際には維持できません。

于 2011-03-15T18:40:41.053 に答える
6

明らかに、マップは一定時間のルックアップを提供しますが、ループは提供しません。値がほとんどない典型的な列挙型では、トラバーサル ルックアップに問題は見られません。

于 2011-03-15T18:36:24.990 に答える
3

これは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");

    }
}
于 2015-05-11T14:17:41.737 に答える
1

Java 8 では、次のファクトリ メソッドを列挙型に追加し、ルックアップ マップをスキップします。

public static Optional<Status> of(int value) {
    return Arrays.stream(values()).filter(v -> value == v.getCode()).findFirst();
}
于 2017-04-21T07:19:22.053 に答える
-2

どちらの方法も完全に有効です。そして、技術的には Big-Oh の実行時間は同じです。

ただし、最初にすべての値を Map に保存すると、ルックアップを行うたびにセットを反復するのにかかる時間を節約できます。ですから、静的マップとイニシャライザの方が少し良い方法だと思います。

于 2011-03-15T18:35:35.607 に答える