如何通过其属性获取枚举类型?

时间:2011-10-25 11:28:46

标签: java enums

我编写了一个枚举类,我希望按类型获取属性并获取type by属性,但似乎不可能。

public enum AreaCode {
    area1(7927),
    area2(7928),
    area3(7929);

    private final int ac;

    AreaCode(int ac) {
        this.ac = ac;
    }

    int areaCode(){
        return ac;
    }

    AreaCode area(int n) {
        switch (n) {
            case 7927: return AreaCode.area1;
            case 7928: return AreaCode.area2;
            case 7929: return AreaCode.area3;
        }
    }
}

上面的代码无法编译。如何让area(int n)工作?

12 个答案:

答案 0 :(得分:28)

除了其他海报所指出的问题外,我会重写方法以避免重复信息(keep it DRY!):

public static AreaCode area(int n) {
  for (AreaCode c : values()) {
    if (c.ac == n) {
      return c;
    }
  }
  // either throw the IAE or return null, your choice.
  throw new IllegalArgumentException(String.valueOf(n));
}

答案 1 :(得分:14)

正如他们所说,给猫皮肤化的方法不止一种。首先,枚举值应该是大写的(由下划线分隔的单词),因为它们是常量值,并且应该通过Java命名约定来对待它们。至少,它们应该以大写字母开头,因为所有的类名都应该。

public enum AreaCode {
    AREA_1(7927),
    AREA_2(7928),
    AREA_3(7929);

    private int areaCode;

    private AreaCode(int areaCode) {
        this.areaCode = areaCode;
    }

    public int getAreaCode() {
        return areaCode;
    }
}

现在,有三种方法可以通过实例变量检索枚举。 switch语句,具有相等条件的循环和查找映射。最后一个场景可能会为您的程序添加更多内存,但如果您需要快速查找大量枚举,这将有助于您以恒定的速率O(1)时间执行此操作。

下面的每个枚举类都是相同的,但每个类都在内部做了不同的事情。通过将以下main()方法添加到这些类中的任何一个,您将得到相同的结果。

public static void main(String[] args) {
    System.out.println(retrieveByAreaCode(7928));
}

上面的示例将打印:

AreaCode[name="AREA_2", value="7928"]

开关

查找是O(1)(常数时间),但您需要对每个案例进行硬编码(不是非常动态)。

public enum AreaCode {
    AREA_1(7927),
    AREA_2(7928),
    AREA_3(7929);

    private int areaCode;

    private AreaCode(int areaCode) {
        this.areaCode = areaCode;
    }

    public int getAreaCode() {
        return areaCode;
    }

    public static AreaCode retrieveByAreaCode(int n) {
        switch (n) {
            case 7927:
                return AreaCode.AREA_1;
            case 7928:
                return AreaCode.AREA_2;
            case 7929:
                return AreaCode.AREA_3;
            default:
                return null;
        }
    }

    @Override
    public String toString() {
        return String.format("%s[name=\"%s\", value=\"%d\"]",
                this.getClass().getName(), this.name(), this.getAreaCode());
    }
}

循环

查找是O(n)(线性时间),因此您需要遍历每个值,直到找到匹配项,但您确实需要对每个案例进行硬编码(动态)。

public enum AreaCode {
    AREA_1(7927),
    AREA_2(7928),
    AREA_3(7929);

    private int areaCode;

    private AreaCode(int areaCode) {
        this.areaCode = areaCode;
    }

    public int getAreaCode() {
        return areaCode;
    }

    public static AreaCode retrieveByAreaCode(int n) {
        for (AreaCode areaCode : AreaCode.values()) {
            if (areaCode.getAreaCode() == n) {
                return areaCode;
            }
        }

        return null;
    }

    @Override
    public String toString() {
        return String.format("%s[name=\"%s\", value=\"%d\"]",
                this.getClass().getName(), this.name(), this.getAreaCode());
    }
}

查找

查找是O(1)(常量时间),您不需要对每个值进行硬编码(动态),但是您需要存储占用内存的地图。

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public enum AreaCode {
    AREA_1(7927),
    AREA_2(7928),
    AREA_3(7929);

    private static final Map<Integer, AreaCode> LOOKUP_MAP;
    private int areaCode;

    static {
        LOOKUP_MAP = new HashMap<Integer, AreaCode>();
        for (AreaCode areaCode : AreaCode.values()) {
            LOOKUP_MAP.put(areaCode.getAreaCode(), areaCode);
        }
        LOOKUP_MAP = Collections.unmodifiableMap(LOOKUP_MAP);
    }

    private AreaCode(int areaCode) {
        this.areaCode = areaCode;
    }

    public int getAreaCode() {
        return areaCode;
    }

    public static AreaCode retrieveByAreaCode(int n) {
        return LOOKUP_MAP.get(n);
    }

    @Override
    public String toString() {
        return String.format("%s[name=\"%s\", value=\"%d\"]",
                this.getClass().getName(), this.name(), this.getAreaCode());
    }
}

通用方法

EnumUtils.java

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class EnumUtils {
    public static interface EnumProperty<T extends Enum<T>, U> {
        U getValue(T type);
    }

    public static <T extends Enum<T>, U> Map<U, T> createLookup(Class<T> enumTypeClass, EnumProperty<T, U> prop) {
        Map<U, T> lookup = new HashMap<U, T>();

        for (T type : enumTypeClass.getEnumConstants()) {
            lookup.put(prop.getValue(type), type);
        }

        return Collections.unmodifiableMap(lookup);
    }
}
import java.util.Map;

public enum AreaCode {
    AREA_1(7927),
    AREA_2(7928),
    AREA_3(7929);

    private static final EnumUtils.EnumProperty<AreaCode, Integer> ENUM_PROP;
    private static final Map<Integer, AreaCode> LOOKUP_MAP;

    static {
        ENUM_PROP = new EnumUtils.EnumProperty<AreaCode, Integer>() {
            @Override
            public Integer getValue(AreaCode code) {
                return code.getAreaCode();
            }
        };
        LOOKUP_MAP = EnumUtils.createLookup(AreaCode.class, ENUM_PROP);
    }

    private int areaCode;

    private AreaCode(int areaCode) {
        this.areaCode = areaCode;
    }

    public int getAreaCode() {
        return areaCode;
    }

    public static AreaCode retrieveByAreaCode(int n) {
        return LOOKUP_MAP.get(n);
    }

    @Override
    public String toString() {
        return String.format("%s[name=\"%s\", value=\"%d\"]",
                this.getClass().getName(), this.name(), this.getAreaCode());
    }
}

答案 2 :(得分:11)

您需要做的就是添加一个默认大小写,以便该方法始终返回一些内容或抛出异常:

AreaCode area(int n){
    switch (n) {
    case 7927: return AreaCode.area1;
    case 7928: return AreaCode.area2;
    case 7929: return AreaCode.area3;
    default: return null;
    }
}

或者更好

AreaCode area(int n){
    switch (n) {
    case 7927: return AreaCode.area1;
    case 7928: return AreaCode.area2;
    case 7929: return AreaCode.area3;
    default: throw new IllegalArgumentException(String.valueOf(n));
    }
}

答案 3 :(得分:4)

我建议添加将整数映射到区域代码的静态地图,然后使用此地图。

public enum AreaCode {
area1(7927), area2(7928), area3(7929);
private final int ac;
private static Map<Integer, AreaCode> id2code = new HashMap<Integer, AreaCode>();

AreaCode(int ac) {
    this.ac = ac;
    id2code.put(ac, this);
}

int areaCode(){
    return ac;
}

AreaCode area(int n){
     return id2code.get(n);

    }
}

}

答案 4 :(得分:2)

它不编译的原因是缺少return语句。您只返回被识别的案例。我建议你添加一个默认情况,返回一些表明区号不知道的情况。名称为unknown或null的枚举常量可以完成这项工作。

答案 5 :(得分:1)

该方法应该是静态的,并且应该在每种情况下返回一些内容。让它在默认情况下返回null,或者使它抛出IllegalArgumentException(或其他一些异常):这取决于你。

注意:阅读编译器错误消息应该为您提供指导。

答案 6 :(得分:1)

我编写了一个帮助程序,以避免污染枚举代码,并允许您按属性获取任何类型的枚举。您将不再需要在每个枚举类型上声明特定方法。

在您的情况下,您可以像下面一样使用它(您的getter必须是公开的):

// 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

您还可以指定要使用的属性(默认情况下,它是枚举的第一个属性)

// 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),所以性能比标准代码片段慢。 从规模来看,更昂贵

但是可以添加缓存系统(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");
    }

}

结果如下: 琐事:28ms |常规:53ms |功能:171ms |反思(论点):890ms |反思(方法):800ms

这个基准测试显示功能解决方案比常规解决方案贵一点(在枚举中有丑陋的代码......),但它仍然可以接受。具有反射的解决方案很美观,但它不适合有时间限制的环境。

答案 7 :(得分:1)

这是另一种方法(使用Guava和Java 8)为查找创建Map:

import com.google.common.collect.Maps;

public enum AreaCode {
  area1(7927),
  area2(7928),
  area3(7929);

  private final int ac;

  private final static Map<Integer, AreaCode> AREA_BY_CODE =
      Maps.uniqueIndex(EnumSet.allOf(AreaCode.class), AreaCode::areaCode);

  AreaCode(int ac) {
    this.ac = ac;
  }

  public static AreaCode area(int n) {
    return AREA_BY_CODE.get(n);
  }

  int areaCode() {
    return ac;
  }
}

答案 8 :(得分:0)

你应该在你的switch语句中包含一个default子句,因为当n不是7927,7928或7929时,编译器不会找不到该做什么。

我希望它有所帮助。

答案 9 :(得分:0)

您可以使用下一个构造

public enum AreaCode {
area1(7927), area2(7928), area3(7929);

private static final Map&lt;Integer, AreaCode> idMap = new HashMap<AreaCode>();

static {
    for (AreaCode areaCode : AreaCode.values()) {
        idMap.put(areaCode.id, areaCode);
    }
}

private Integer id;
private AreaCode(Integer id) {
    this.id = id;
}

public static AreaCode getById(Integer id) {
    return idMap.get(id);
}

答案 10 :(得分:0)

这样,您可以在方法签名上清楚地表明您可能会找到枚举。

 public static Optional<AreaCode> getAreaCode(int area_code){
       return Arrays.stream(AreaCode.values()).filter(e -> e.ac == area_code).findFirst();
  }

答案 11 :(得分:-1)

public static AreaCode retrieveByAreaCode(int n) {
    switch (n) {
        case AreaCode.AREA_1.getAreaCode():
            return AreaCode.AREA_1;
        case AreaCode.AREA_2.getAreaCode():
            return AreaCode.AREA_2;
        case AreaCode.AREA_3.getAreaCode():
            return AreaCode.AREA_3;
        default:
            return null;
    }
}