Nice programing

Java 열거 형 역방향 조회 모범 사례

nicepro 2020. 10. 30. 20:58
반응형

Java 열거 형 역방향 조회 모범 사례


블로그 에서 다음이 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;
    }
}

두 방법 중 하나에 명백한 문제가 있습니까? 이러한 종류의 조회를 구현하는 권장 방법이 있습니까?


Google GuavaMaps.uniqueIndex조회 맵을 작성하는 데 매우 유용합니다.

업데이트 : 다음은 Maps.uniqueIndexJava 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);
    }
}

오버 헤드가 높지만 정적 맵은 code. 구현의 조회 시간은 열거 형의 요소 수에 따라 선형 적으로 증가합니다. 작은 열거 형의 경우 이것은 단순히 크게 기여하지 않습니다.

두 구현 (그리고 일반적으로 Java 열거 형)의 한 가지 문제는 a Status가 취할 수 있는 숨겨진 추가 값이 있다는 것입니다 null. 비즈니스 로직의 규칙에 따라 실제 열거 형 값을 반환하거나 Exception조회가 "실패"할 때를 throw하는 것이 합리적 일 수 있습니다 .


더 빠른 대안이 있습니다.

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

두 가지 방법 모두 완벽하게 유효합니다. 그리고 그들은 기술적으로 동일한 Big-Oh 실행 시간을 가지고 있습니다.

그러나 먼저 모든 값을 맵에 저장하면 조회를 수행 할 때마다 집합을 반복하는 데 걸리는 시간을 절약 할 수 있습니다. 그래서 저는 정적 맵과 이니셜 라이저가 약간 더 나은 방법이라고 생각합니다.

참고 URL : https://stackoverflow.com/questions/5316311/java-enum-reverse-look-up-best-practice

반응형