Java enum reverse look-up best practice

JavaEnumsStatic Initializer

Java Problem Overview


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?

Java Solutions


Solution 1 - Java

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

Solution 2 - Java

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."

Solution 3 - Java

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.

Solution 4 - Java

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.

Solution 5 - Java

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

Solution 6 - Java

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

Solution 7 - Java

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

Solution 8 - Java

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionArmandView Question on Stackoverflow
Solution 1 - Javauser638455View Answer on Stackoverflow
Solution 2 - JavaMatt BallView Answer on Stackoverflow
Solution 3 - JavaPaŭlo EbermannView Answer on Stackoverflow
Solution 4 - JavadigitaljoelView Answer on Stackoverflow
Solution 5 - JavaVitaliy OliynykView Answer on Stackoverflow
Solution 6 - Javace72View Answer on Stackoverflow
Solution 7 - JavaNILESH SALPEView Answer on Stackoverflow
Solution 8 - JavajjnguyView Answer on Stackoverflow