【问题标题】:How to get the enum type by its attribute?如何通过其属性获取枚举类型?
【发布时间】:2011-10-25 11:28:46
【问题描述】:

我写了一个枚举类,我想要么按类型获取属性,要么按属性获取类型,但似乎不可能。

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实例字段。
  • 枚举常量是常量,应该写成 UPPER_SNAKE_CASE。

标签: java enums


【解决方案1】:

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

【讨论】:

  • 有效,但是如果有大量的区号,这将变得低效。地图似乎更好。
  • @G_H:效率低下非常不太可能对应用程序的整体性能产生任何影响,除非你真的有数千个区号(即使那样,你可能不会注意到它)。如果 that 是这种情况,那么您很可能一开始就不应该为此使用enum
  • 关于不使用枚举的区号数量是正确的。尽管如此,Map 仍然是一个干净的解决方案,它限制了代码量,并且可以让以后更容易迁移到常规类。应该避免过早的优化,但在我看来为问题选择最合适的数据结构并不是为时过早。
  • @G_H 我完全同意,尤其是因为使用地图不会有更多的开销。
  • 如果area() 函数只占应用程序总执行时间的一小部分(这很可能),那么将函数优化为哈希映射不太可能提高实际应用程序的性能,甚至如果函数快一个数量级。 "...Map 是一种限制代码量的干净解决方案" 的命题并没有考虑到任何优化的取舍性质。也就是说,在这种情况下,您正在用执行时间换取更长的初始化时间和更大的内存占用(在特定环境中很重要)。
【解决方案2】:

正如他们所说,给猫剥皮的方法不止一种。首先,枚举值应该是大写的(用下划线分隔的单词),因为它们是常量值,应该按照 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)(恒定时间),不需要对每个值进行硬编码(动态),但需要存储占内存的map。

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

【讨论】:

  • 这三种方法的表现非常好。我唯一的评论是查找方法中的静态初始化程序块中的循环可能是无关的 - 使用枚举构造函数来填充地图,因为这个答案显示更巧妙:stackoverflow.com/a/7888738/1563178
  • 是的,我知道这种方法会将它们添加到构造函数内的映射中,但是...该映射是可变的,这可能会导致线程问题。我的实现实际上更流畅,因为我使用了一个静态块,在我看来,它更聪明,因为地图是在密封之前填充的。
  • 嗯,枚举实例都是在静态初始化期间构造的,所以它应该都发生在同一个线程上。即便如此,将您的地图声明为最终地图并不会使其不可变 - 您需要通过 Collections.unmodifiableMap() 调用将其传递以“密封”并防止对其内容进行修改
【解决方案3】:

您需要做的就是添加一个默认情况,以便该方法始终返回某些内容或引发异常:

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

【讨论】:

  • 喜欢你的 IllegalArgumentException 方法。
  • 这个解决方案的主要问题是它不健壮并且它代表一个潜在的维护问题。每次添加新的枚举常量时,都需要编辑 switch-case 块。 @Joachim Sauer 发布的解决方案的优点是它适用于任意数量的枚举常量,即使枚举随时间演变。
【解决方案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);

    }
}

}

【讨论】:

  • 虽然答案已经被接受,但我想说这是最有效的方法。每个枚举常量都需要一个 switch case,这会导致大量重复。这将自动启动地图,并且它具有最佳的反向查找性能。
  • 不能引用初始化器中的静态枚举字段 AreaCode.id2code
【解决方案5】:

我写了一个帮助程序来避免污染枚举代码,它可以让你通过属性获取任何类型的枚举。您将不再需要为每个枚举类型声明特定方法。

在你的情况下,你可以像下面这样使用它(你的吸气剂必须是公开的):

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

你也可以指定你想使用哪个属性(默认是Enum的first属性)

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

}

结果是: 微不足道:28ms |常规:53ms |功能:171ms |反射(参数):890ms |反射(方法):800ms

这个基准测试表明,函数式解决方案比常规解决方案贵一点(枚举中的代码很丑......),但它仍然可以接受。带反射的解决方案读起来很漂亮,但不适合有时间限制的环境。

【讨论】:

    【解决方案6】:

    这是另一种方法(使用 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;
      }
    }
    

    【讨论】:

      【解决方案7】:

      它无法编译的原因是缺少 return 语句。您只为已识别的案例返回一些东西。我建议您添加一个默认情况,该情况返回指示区号未知的内容。名称为 unknown 或 null 的枚举常量都可以完成这项工作。

      【讨论】:

        【解决方案8】:

        你可以使用下一个构造

        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。
        • 哦不好意思,当然应该是idMap.put(areaCode.id, areaCode);而不是 idMap.put(AreaCode.id, areaCode);
        【解决方案9】:

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

        注意:阅读编译器错误消息应该可以指导您。

        【讨论】:

          【解决方案10】:

          这样你就可以清楚地表明你可能会或找不到枚举的方法签名。

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

          【讨论】:

            【解决方案11】:

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

            【讨论】:

              猜你喜欢
              • 2020-07-19
              • 2016-07-07
              • 1970-01-01
              • 1970-01-01
              • 2014-02-27
              • 2020-12-31
              • 2011-02-16
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多