Я написал помощника, чтобы избежать загрязнения кода enum, и который позволяет вам получать Enum любого типа по атрибуту. Вам больше не нужно будет объявлять конкретные методы для каждого типа перечисления.
В вашем случае вы можете использовать его следующим образом (ваш геттер должен быть публичным):
// 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
подробности:
Дано следующее перечисление:
public enum Move {
FORWARD("F"),
RIGHT("R"),
LEFT("L");
private String label;
private Move(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}
Помощники
В Java 8: использует функциональное программирование
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);
}
}
Использование:
Move move = FunctionalEnumHelper.getEnum(Move.class, Move::getLabel, "F") // value is Move.FORWARD
В Java 6: использует 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;
}
}
}
Использование:
// Basic use
Move move = EnumHelper.getEnum(Move.class, "F"); // value is Move.FORWARD
Вы также можете указать, какой атрибут вы хотите использовать (по умолчанию это атрибут first в 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");
Преимущества
Код централизован , и вам не нужно кодировать одну и ту же обработку в каждом перечислении. Не изобретай велосипед!
прост в использовании , увеличивает производительность, а перечисления остаются чистыми .
Недостатки
Время выполнения:
Сложность составляет O (n) , поэтому нет доступа к статической хэш-карте, объявленной в типе перечисления (то есть O (1)).
В противном случае, поскольку он использует API отражения (java 6) или Functional (java 8), производительность ниже, чем при использовании стандартного фрагмента кода.
Это на 10 * дороже .
Однако возможно добавить кеш-систему (EhCache, map ..).
Безопасность:
Этот помощник может генерировать исключения во время выполнения, если вы вызываете его с неверными аргументами, в то время как стандартный код вызвал бы ошибки во время компиляции.
Тест производительности
API отражения медленный , поэтому он не является дружественным для производства!
Не используйте его в производственной среде с временными ограничениями.
Просто добавьте статический метод Move :: getMove для сравнения тестов:
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;
}
}
Теперь мы можем сравнить производительность каждого решения:
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");
}
}
Результаты:
Trivial: 28 мс | Регулярно: 53мс | Функциональный: 171мс | Отражение (аргумент): 890мс | Отражение (метод): 800 мс
Этот тест показывает, что функциональное решение немного дороже, чем обычное решение (с уродливым кодом в перечислениях ...), но оно остается приемлемым. Решение с отражением приятно читать, но оно не подходит для среды с ограничением по времени.