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

18

我已经编写了一个枚举类,我想通过类型获取属性或通过属性获取类型,但似乎这是不可能的。

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)函数工作?


你的 area() 方法应该是 static 的(即静态工厂),否则你总是需要一个 AreaCode 实例才能通过它的实例字段获取 AreaCode - scottb
可能是 Get enum by its inner field 的重复问题。 - scottb
枚举常量是常量,应该使用大写蛇形命名法编写。 - MC Emperor
11个回答

43

除了其他帖子中指出的问题,我会重写该方法以避免重复信息(保持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));
}

5
可行,但如果存在大量区号,则效率将变得低下。使用映射(Map)可能更好。 - G_H
3
@G_H: 除非你有成千上万个地区码(即使是这样,你可能也不会注意到),否则那种低效几乎不可能对你的应用程序整体性能产生任何影响。如果是这种情况,那么很可能你在一开始就不应该使用“枚举”。 - Joachim Sauer
4
不使用枚举来处理区号数量确实是正确的。但是,使用 Map 是一个简洁的解决方案,它可以限制代码量,并且在以后转移到常规类时更容易。应避免过早优化,但是在我看来选择最合适的数据结构并不算过早优化。 - G_H
1
@G_H 我完全同意,特别是使用 Map 不会增加太多开销。 - Stefan
2
如果area()函数占总应用程序执行时间的一小部分(这很可能是),那么将该函数优化为哈希映射很可能不会提高实际应用程序性能,即使该函数快了一个数量级。主张“...Map是一个干净的解决方案,可以限制代码量”并没有考虑到任何优化的得失性质。也就是说,在这种情况下,您正在交换执行时间以获得更长的初始化时间和更大的内存占用(在特定环境中非常重要)。 - scottb

26

俗话说得好,“杀猫有多种方法。”首先,枚举值应该是大写的(用下划线分隔的单词),因为它们是常量值,应该按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());
    }
}

三种方法的运行情况很好。我唯一的评论是,在查找方法中静态初始化程序块中的循环可能是多余的 - 使用枚举构造函数来填充地图,就像这个答案所示,会更加流畅:https://dev59.com/r2sz5IYBdhLWcg3wcndb#7888738 - icyitscold
是的,我知道那种方法是在构造函数内将它们添加到映射中,但是...那个映射是可变的,这可能会导致线程问题。我的实现更加巧妙,因为我使用了静态块,在我看来,这更聪明,因为在封装之前就填充了映射。 - Mr. Polywhirl
枚举实例也是在静态初始化期间构造的,因此应该在同一个线程上执行。即便如此,将你的 map 声明为 final 并不代表它是不可变的 - 你需要通过Collections.unmodifiableMap()方法将其传递,以“封装”它并防止修改其内容。 - icyitscold

16

您所需做的就是添加一个默认情况,使得该方法始终返回某个值或抛出异常:

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

1
喜欢你的非法参数异常处理方式。 - Efthymis
3
这个解决方案的主要问题在于它不够健壮,可能会带来维护问题。每次添加一个新的枚举常量时,都需要编辑switch-case块。@Joachim Sauer发布的解决方案的优点是它对任意数量的枚举常量都有效,即使枚举随着时间的推移而发展也是如此。 - scottb

4

以下是使用Guava和Java 8创建查找映射表的另一种方法:

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

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

    }
}

}

1
虽然已经有了一个被接受的答案,但我认为这是最有效的方法。你需要为每个枚举常量编写switch case,这会导致很多重复。这将自动设置map,并且对于反向查找具有最佳性能。 - G_H
7
无法在初始化器中引用静态枚举字段AreaCode.id2code。 - Thetsu

4

我已经写了一个助手来避免污染枚举代码,并使您可以通过属性获取任何类型的枚举。您将不再需要在每个枚举类型上声明特定的方法。

在您的情况下,您可以使用以下方式(您的getter必须是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

详情:

给定以下枚举:

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)或函数式(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");
    }

}
结果如下: 琐碎的解决方案:28毫秒 | 常规解决方案:53毫秒 | 函数式解决方案:171毫秒 | 反射(参数)解决方案:890毫秒 | 反射(方法)解决方案:800毫秒。
此基准测试显示,函数式解决方案比常规解决方案(在枚举中使用丑陋的代码)略微昂贵,但仍然可以接受。反射解决方案阅读起来美观,但不适用于时间受限的环境。

2
你可以使用下面的结构。
public enum AreaCode {
  area1(7927),
  area2(7928),
  area3(7929);

  private static final Map<Integer, AreaCode> idMap = new HashMap<Integer, 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);
  }
}

你的静态块引用了非静态字段id。 - Thetsu
哦,不好意思,当然应该是 idMap.put(areaCode.id, areaCode); 而不是 idMap.put(AreaCode.id, areaCode);。 - PonomarevMM

2
它无法编译的原因是缺少返回语句。 只有识别的情况下才会返回值。建议添加一个默认情况,返回一个指示区号未知的值。可以使用名称为“unknown”的枚举常量或null。

1

这个方法应该是静态的,并且在任何情况下都应该返回一些东西。可以让它在默认情况下返回null,或者让它抛出IllegalArgumentException(或其他异常):由你决定。

注意:阅读编译器错误消息应该会指导你。


1

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接