在地图中使用通用枚举类

4
我有一个将类名映射到它们的枚举类的映射表,我还有一个将字符串(例如"SomeEnum.FIRST")解析为实际对象的方法。但是,Enum.valueOf不接受Class>,而映射表无法存储Class>。
对于代码部分,映射表大致如下:
    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(Android Studio)会提示我:“Unchecked method 'parse(List)' invocation”。据我所知,这是由于泛型类型造成的。如果我在parse中删除它,它将无法编译,但警告会消失。有没有什么好的解决方法呢?


2
它是未选中的,因为编译器无法确保“strParameters”仅包含给定“T”的条目。此外,如果调用者知道枚举,即“T”(否则您无法调用它),为什么需要在映射中查找?重新考虑您正在做的事情。可能性是您应该简单地删除“T”。 - Andreas
@Andreas如果我在parse中删除TEnum.valueOf会抱怨该类无效。 - xZise
2个回答

4
如果你有类似以下的枚举类型:
  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 的警告。通常最好在尽可能有限的作用域内应用警告抑制。在本例中,它仅用于单个赋值语句。虽然这在一般情况下可能会引起注意,但在当前情况下,我们知道 map 中唯一的值实际上是枚举类,因为它们必须是通过 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");

该代码被编译,但在运行时出现了类强制转换异常。


1

没有安全的方式可以拥有依赖于相应键的泛型类型的Map值。

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

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, ?>>。每个值都是一个由常量名称为键的枚举常量的 Map。 ? 足够了; 记住常量值是枚举类型没有好处,因为 parse 返回 Object[]
  • registerEnum 有一个泛型类型,以保证其参数是有效的枚举类型。它不再存储类参数,而是存储该枚举的常量。
  • parse 不需要泛型类型,因为它返回 Object[]
  • parse 不使用任何 Enum 方法,因此不再关注泛型类型安全性。
  • 我修复了一个错误: strParameter.substring(0, delim); 而不是 delim - 1。你想要直到但不包括句号的整个子字符串。

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