Лучшие практики обратного просмотра Java enum - PullRequest
70 голосов
/ 15 марта 2011

Я видел, что предложил в блоге , что следующий подход был разумным способом сделать "обратный поиск", используя 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;
    }
}

Есть ли какие-либо очевидные проблемы с любым из этих методов, и есть ли рекомендуемый способ реализации этого вида поиска?

Ответы [ 8 ]

23 голосов
/ 15 марта 2011

Maps.uniqueIndex от Google Гуава очень удобна для создания справочных карт.

Обновление: вот пример использования 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);
    }
}
19 голосов
/ 15 марта 2011

Несмотря на более высокие издержки, статическая карта хороша тем, что предлагает поиск в постоянном времени на code.Время поиска вашей реализации увеличивается линейно с количеством элементов в перечислении.Для небольших перечислений это просто не будет значительным.

Одна проблема с обеими реализациями (и, возможно, с перечислениями Java в целом) заключается в том, что на самом деле есть скрытое дополнительное значение, которое может принимать Status:null.В зависимости от правил бизнес-логики может иметь смысл возвращать фактическое значение перечисления или выдавать Exception, когда поиск "не удается".

8 голосов
/ 15 марта 2011

Вот альтернатива, которая может быть даже немного быстрее:

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

Конечно, это не совсем поддерживаемо, если вы хотите иметь возможность добавить больше констант позже.

5 голосов
/ 15 марта 2011

Очевидно, что карта будет обеспечивать постоянный поиск по времени, а цикл - нет. В типичном перечислении с несколькими значениями я не вижу проблемы с поиском обхода.

3 голосов
/ 11 мая 2015

Вот альтернатива 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");

    }
}
0 голосов
/ 13 мая 2019

Каждый другой ответ в этой теме использовал статический метод.Я думал, что мы должны предоставить пример с использованием метода экземпляра, в качестве альтернативы:

public class Main {
    public static void main(String[] args) {
        for (Alphabet item : Alphabet.values()) {
            System.out.println("GET: " + item.getValue() );
            System.out.println("REV: " + item.reverseLookup(item.getValue()) );
        }
    }
}


enum Alphabet {
    X(24) {
        public String reverseLookup(int o) {
            return "X";
        }
    },
    Y(25) {
        public String reverseLookup(int o) {
            return "Y";
        }
    },
    Z(26) {
        public String reverseLookup(int o) {
            return "Z";
        }
    };

    abstract String reverseLookup(int o);

    private final int value;

    Alphabet(final int newValue) {
        value = newValue;
    }

    public int getValue() { return value; }
}
0 голосов
/ 21 апреля 2017

В Java 8 я просто добавил бы следующий метод фабрики к вашему перечислению и пропустил поиск Map.

public static Optional<Status> of(int value) {
    return Arrays.stream(values()).filter(v -> value == v.getCode()).findFirst();
}
0 голосов
/ 15 марта 2011

Оба способа абсолютно верны. И у них технически одно и то же время Big-Oh.

Однако, если вы сначала сохраните все значения на карте, вы сэкономите время, необходимое для итерации набора каждый раз, когда вы хотите выполнить поиск. Итак, я думаю, что статическая карта и инициализатор - немного лучший путь.

...