I have written a helper to avoid to pollute enum code and which lets you get Enum of any type by attribute. You won't have anymore to declare specific methods on each enum type.
In your case, you can use it like following (your getter must be public):
// Java 8
AreaCode area = FunctionalEnumHelper.getEnum(AreaCode.class, AreaCode::areaCode, 7927); // value is area1
// Java 6
AreaCode area = EnumHelper.getEnum(AreaCode.class, 7927); // value is area1
Details:
Given the following enum :
public enum Move {
FORWARD("F"),
RIGHT("R"),
LEFT("L");
private String label;
private Move(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}
Helpers
With Java 8: uses functional programming
import java.util.function.Function;
/**
* Helper to get an {@link Enum} of any Type by attribute or method
*
*/
public final class FunctionalEnumHelper {
// Constructors
//-------------------------------------------------
/**
* Private constructor
* A helper should not be instantiated in order to force static calls
*/
private FunctionalEnumHelper() {}
// Static methods
//-------------------------------------------------
/**
* Get the enum of type <code>E</code> associated to the attribute
* @param enumType
* @param method
* @param expectedValue
* @return
*/
public static <E extends Enum<E>, R> E getEnum(final Class<E> enumType, final Function<E, R> method, final R expectedValue) {
E enumVariable = null;
E[] values = enumType.getEnumConstants();
if(values != null) {
for(E e : values) {
if(e != null) {
Object value = method.apply(e);
if(value == null && expectedValue == null || value != null && value.equals(expectedValue)) {
enumVariable = e;
break;
}
}
}
}
return enumVariable;
}
/* Functional style */
public static <E extends Enum<E>, R> E getEnum(final Class<E> enumType, final Function<E, R> method, final R expectedValue) {
return Arrays.stream(enumType.getEnumConstants())
.filter(e -> {
final Object value = method.apply(e);
return value == null && expectedValue == null || value != null && value.equals(expectedValue);
})
.findAny()
.orElse(null);
}
}
Use:
Move move = FunctionalEnumHelper.getEnum(Move.class, Move::getLabel, "F") // value is Move.FORWARD
With Java 6: uses the reflection API
import java.lang.annotation.ElementType;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper to get an {@link Enum} of any Type by attribute or method
*
*/
public final class EnumHelper {
private static final Logger logger = LoggerFactory.getLogger(EnumHelper.class);
// Constructors
//-------------------------------------------------
/**
* Private constructor
* A helper should not be instantiated in order to force static calls
*/
private EnumHelper() {}
// Static methods
//-------------------------------------------------
/**
* Get the enum of type <code>E</code> which first attribute has value {@link obj}.
* @param enumType
* @param value
* @return
*/
public static <E extends Enum<E>> E getEnum(final Class<E> enumType, final Object value) {
return getEnum(enumType, null, value);
}
/**
* Get the enum of type <code>E</code> which attribute {@link attributeName} has value {@link obj}.
* @param enumType
* @param value
* @return
*/
public static <E extends Enum<E>> E getEnum(final Class<E> enumType, final String attributeName, final Object value) {
return getEnum(enumType, ElementType.FIELD, attributeName, value);
}
/**
* Get the enum of type <code>E</code> associated to the attribute
* @param enumType
* @param elementType
* @param name
* @param value
* @return
*/
public static <E extends Enum<E>> E getEnum(final Class<E> enumType, final ElementType elementType, final String name, final Object value) {
E enumVariable = null;
E[] enumObjs = enumType.getEnumConstants();
if(enumObjs != null) {
ReflectionData reflectionData = new ReflectionData();
for(E enumObj : enumObjs) {
if(enumObj != null) {
Object val = getValue(reflectionData, elementType, name, enumObj);
if(val == null && value == null || val != null && val.equals(value)) {
enumVariable = enumObj;
break;
}
}
}
}
return enumVariable;
}
private static Object getValue(final ReflectionData reflectionData, final ElementType type, final String name, final Object obj) {
Object value = null;
final String parameter = name != null ? name.trim() : null;
switch(type) {
case FIELD : value = getFieldValue(reflectionData, obj, parameter); break;
case METHOD : value = callMethod(reflectionData, obj, parameter); break;
default : throw new IllegalArgumentException("The elementType '" + type.toString() + "' is not allowed!");
}
return value;
}
/**
* Get the attribute value
* @param reflectionData
* @param obj
* @param fieldName
* @return
*/
private static Object getFieldValue(final ReflectionData reflectionData, final Object obj, final String fieldName) {
Object value = null;
try {
Field field = reflectionData.getField();
if(field == null) {
field = computeField(obj, fieldName);
reflectionData.setField(field);
}
boolean accessible = field.isAccessible();
field.setAccessible(true);
value = field.get(obj);
field.setAccessible(accessible);
}
catch (NoSuchFieldException | SecurityException e) {
logger.error("No attribute {} : {}", fieldName, e.getMessage());
}
catch (IllegalArgumentException | IllegalAccessException e) {
logger.error(e.getMessage());
}
return value;
}
private static Field computeField(final Object obj, final String fieldName) throws NoSuchFieldException {
Field field = null;
if(fieldName != null) {
field = obj.getClass().getDeclaredField(fieldName);
}
else {
Field[] fields = obj.getClass().getDeclaredFields();
if(fields != null) {
int position = obj.getClass().getEnumConstants().length;
field = fields[position];
}
}
return field;
}
/**
* Call the method
* @param reflectionData
* @param obj
* @param methodName
* @return
*/
private static Object callMethod(final ReflectionData reflectionData, final Object obj, final String methodName) {
Object returnValue = null;
try {
Method method = reflectionData.getMethod();
if(method == null) {
method = obj.getClass().getMethod(methodName);
reflectionData.setMethod(method);
}
returnValue = method.invoke(obj);
}
catch (SecurityException | NoSuchMethodException e) {
logger.error("No method {} : {}", methodName, e.getMessage());
}
catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
logger.error("Error when calling method {} on class {} : {}", methodName, obj.getClass(), e.getMessage());
}
return returnValue;
}
private static class ReflectionData {
private Field field;
private Method method;
public Field getField() {
return field;
}
public Method getMethod() {
return method;
}
public void setField(final Field field) {
this.field = field;
}
public void setMethod(final Method method) {
this.method = method;
}
}
}
Use:
// Basic use
Move move = EnumHelper.getEnum(Move.class, "F"); // value is Move.FORWARD
You can also specify which attribute you want to use (by default it is the first attribute of the Enum)
// Get by attribute
Move move = EnumHelper.getEnum(Move.class, "label", "F");
// Get by method
Move move = EnumHelper.getEnum(Move.class, ElementType.METHOD, "getLabel", "F");
Benefits
The code is centralised and you don't need to code the same handling into each enumeration. Don't reinvent the wheel !
It is easy to use, it increases the productivity and enums remain cleans.
Disadvantages
Execution time:
The complexity is O(n), so not as well as accessing to a static hashmap declared in an enumeration Type (which is O(1)).
Otherwise, because it uses the reflection API (java 6) or Functional (java 8), performance are slower than with a standard code fragment.
It is, by magnitude, more expensive.
However it is possible to add a cache system (EhCache, map..).
Safety:
This helper can throw exceptions at runtime if you call it with bad arguments, while standard code would have produced errors during the compilation.
Performance test
The reflection API is slow, so it is not production friendly !
Don't use it in a production environment with time constraints.
Just add a static method Move::getMove for test comparison:
public enum Move {
FORWARD("F"),
RIGHT("R"),
LEFT("L");
private String label;
private Move(final String label) {
this.label = label;
}
public String getLabel() {
return label;
}
// Only used by regular test
public static Move getMove(final String label) {
Move move = null;
for(Move curr : Move.values()) {
if(curr.label.equals(label)) {
move = curr;
break;
}
}
return move;
}
}
Now we can compare performance of each solution:
public class Main {
public static void main(final String[] args) {
int nbrIterations = 1000000;
doTrivial(nbrIterations);
doRegular(nbrIterations);
doFunctional(nbrIterations);
doReflection(nbrIterations);
}
private static void doTrivial(final int nbrIterations) {
long start = System.currentTimeMillis();
for (int i=0; i<nbrIterations; ++i) {
Move.valueOf("FORWARD");
Move.valueOf("RIGHT");
Move.valueOf("LEFT");
}
System.out.println("Trivial: " + (System.currentTimeMillis() - start) + "ms");
}
private static void doRegular(final int nbrIterations) {
long start = System.currentTimeMillis();
for (int i=0; i<nbrIterations; ++i) {
Move.getMove("F");
Move.getMove("R");
Move.getMove("L");
}
System.out.println("Regular: " + (System.currentTimeMillis() - start) + "ms");
}
private static void doFunctional(final int nbrIterations) {
long start = System.currentTimeMillis();
for (int i=0; i<nbrIterations; ++i) {
FunctionalEnumHelper.getEnum(Move.class, Move::getLabel, "F");
FunctionalEnumHelper.getEnum(Move.class, Move::getLabel, "R");
FunctionalEnumHelper.getEnum(Move.class, Move::getLabel, "L");
}
System.out.println("Functional: " + (System.currentTimeMillis() - start) + "ms");
}
private static void doReflection(final int nbrIterations) {
long start = System.currentTimeMillis();
for (int i=0; i<nbrIterations; ++i) {
EnumHelper.getEnum(Move.class, "F");
EnumHelper.getEnum(Move.class, "R");
EnumHelper.getEnum(Move.class, "L");
}
System.out.println("Reflection (argument): " + (System.currentTimeMillis() - start) + "ms");
long start2 = System.currentTimeMillis();
for (int i=0; i<nbrIterations; ++i) {
EnumHelper.getEnum(Move.class, ElementType.METHOD, "getLabel", "F");
EnumHelper.getEnum(Move.class, ElementType.METHOD, "getLabel", "R");
EnumHelper.getEnum(Move.class, ElementType.METHOD, "getLabel", "L");
}
System.out.println("Reflection (method): " + (System.currentTimeMillis() - start2) + "ms");
}
}
The results are:
Trivial: 28ms | Regular: 53ms | Functional: 171ms | Reflection (argument): 890ms | Reflection (method): 800ms
This benchmark shows that the functional solution is a little more expensive than the regular solution (with ugly code in enums..), but it remains acceptable. The solution with reflection is beautiful to read but it is not suitable to an environment with time constraint.