有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java在映射中使用通用枚举类

我有一个类名称到它们的枚举类的映射,我有一个方法,可以将像"SomeEnum.FIRST"这样的字符串解析到实际的对象中。但是Enum.valueOf不接受Class<? extends Enum<?>>,而映射不能存储Class<T extends Enum<T>>

对于代码,映射如下所示:

    private static final HashMap<String, Class<? extends Enum<?>>> enumsMap;

    static {
        enumsMap = new HashMap<>();
        // These are two DIFFERENT enum classes!
        registerEnum(SomeEnum.class);
        registerEnum(AnotherEnum.class);
    }

    private static void registerEnum(Class<? extends Enum<?>> enumClass) {
        enumsMap.put(enumClass.getSimpleName(), enumClass);
    }

下面是解析器(删除了不必要的代码):

    public <T extends Enum<T>> Object[] parse(List<String> strParameters) {
        Object[] parameters = new Object[strParameters.size()];
        for (int i = 0; i < parameters.length; i++) {
            String strParameter = strParameters.get(i);
            int delim = strParameter.lastIndexOf('.');
            String className = strParameter.substring(0, delim - 1);
            String enumName = strParameter.substring(delim + 1);
            Class<T> enumClass = (Class<T>) enumsMap.get(className);
            parameters[i] = Enum.valueOf(enumClass, enumName);
        }
        return parameters;
    }

现在如果我调用这个parse,我的IDE(安卓studio)告诉我,“Unchecked method”parse(List)“invocation”,而且afaik这是因为这个泛型类型。如果我在parse中删除它,它将不会编译,但警告将消失。有什么好办法吗


共 (2) 个答案

  1. # 1 楼答案

    没有安全的方法可以使映射值的泛型类型取决于相应的键

    但是,您可以自己存储枚举常量:

    private static final Map<String, Map<String, ?>> enumsMap;
    
    static {
        enumsMap = new HashMap<>();
        // These are two DIFFERENT enum classes!
        registerEnum(SomeEnum.class);
        registerEnum(AnotherEnum.class);
    }
    
    private static <T extends Enum<T>> void registerEnum(Class<T> enumClass) {
        Map<String, ?> valuesByName =
            EnumSet.allOf(enumClass).stream().collect(
                Collectors.toMap(Enum::name, Function.identity()));
        enumsMap.put(enumClass.getSimpleName(), valuesByName);
    }
    
    public Object[] parse(List<String> strParameters) {
        Object[] parameters = new Object[strParameters.size()];
        for (int i = 0; i < parameters.length; i++) {
            String strParameter = strParameters.get(i);
            int delim = strParameter.lastIndexOf('.');
            String className = strParameter.substring(0, delim);
            String enumName = strParameter.substring(delim + 1);
            Map<String, ?> enumValues = enumsMap.get(className);
            parameters[i] = enumValues.get(enumName);
            if (parameters[i] == null) {
                throw new IllegalArgumentException("Class " + className
                    + " does not contain constant " + enumName);
            }
        }
        return parameters;
    }
    

    我改变了什么:

    • enumsMap现在是Map<String, Map<String, ?>>。每个值都是由常量名称键入的枚举常量的映射?就足够了;记住常量值是枚举没有好处,因为parse返回Object[]
    • registerEnum具有泛型类型,以保证其参数是有效的枚举类型。它不存储类参数,而是存储该枚举的常量
    • parse不需要泛型类型,因为它返回Object[]
    • parse不使用任何Enum方法,因此泛型类型安全不再是一个问题
    • 我修复了一个bug:strParameter.substring(0, delim);而不是delim - 1。您希望整个子字符串一直到但不包括句点
  2. # 2 楼答案

    如果您有如下枚举:

      enum Foo {
        A, B, C
      }
    
      enum Bar {
        D, E, F
      }
    

    然后,您可以使用下面的代码实现您正在讨论的映射类型

    class MyEnums {
        private final Map<String, Class<? extends Enum<?>>> map = new HashMap<>();
    
        public void addEnum(Class<? extends Enum<?>> e) {
          map.put(e.getSimpleName(), e);
        }
    
        private <T extends Enum<T>> T parseUnsafely(String name) {
          final int split = name.lastIndexOf(".");
          final String enumName = name.substring(0, split);
          final String memberName = name.substring(split + 1);
          @SuppressWarnings("unchecked")
          Class<T> enumType = (Class<T>) map.get(enumName);
          return Enum.valueOf(enumType, memberName);
        }
    
        public Object parse(String name) {
          return parseUnsafely(name);
        }
    
        public Object[] parseAll(String... names) {
          return Stream.of(names)
              .map(this::parse)
              .collect(toList())
              .toArray();
        }
      }
    

    不过,这并不能绕过未经检查的演员阵容;它只是暂时对你隐藏它。您可以看到SuppressWarnings用于隐藏关于enumType的警告的位置。一般来说,最好在尽可能有限的范围内应用警告抑制。在本例中,它是针对单个任务的。虽然这通常可能是一个危险信号,但在本例中,我们知道映射中唯一的值实际上是枚举类,因为它们必须是由addEnum添加的

    然后,它可以用作:

      MyEnums me = new MyEnums();
      me.addEnum(Foo.class);
      me.addEnum(Bar.class);
      System.out.println(me.parse("Foo.A"));
      System.out.println(me.parse("Bar.E"));
      System.out.println(Arrays.toString(me.parseAll("Foo.B", "Bar.D", "Foo.C")));
    

    其中打印:

    A
    E
    [B, D, C]
    

    你会注意到我把parseUnsafelyparse分成了不同的方法。我们不想直接公开parseUnsafely的原因是它通过其返回类型提供了一个我们实际上无法强制执行的保证。如果它被公开了,那么我们可以编写如下代码

    Bar bar = me.parseUnsafely("Foo.B");
    

    可编译,但在运行时失败,并出现强制转换类异常