在地图中使用通用枚举类

时间:2019-05-13 20:46:38

标签: java generics enums

我有一个类名到其枚举类的映射,并且我有将"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(Android Studio)会告诉我“未经检查的方法'parse(List)'调用”,而afaik这是因为该泛型类型。如果我在parse中将其删除,它将无法编译,但警告会消失。有没有解决的办法?

2 个答案:

答案 0 :(得分: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();
    }
  }

这不会 not 绕过未经检查的演员表;它只是暂时向您隐藏。您可以看到在哪里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");

可以编译,但是在运行时由于强制转换类异常而失败。

答案 1 :(得分: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, ?>>。每个值都是由常量名称作为键的枚举常量的映射。 ?就足够了;记住常数值是枚举是没有好处的,因为parse返回Object[]
  • registerEnum具有泛型类型,以确保其参数为有效的枚举类型。它不存储类参数,而是存储该枚举的常量。
  • parse不需要通用类型,因为它返回Object[]
  • parse不使用任何Enum方法,因此不再关心泛型类型的安全性。
  • 我修复了一个错误:strParameter.substring(0, delim);而不是delim - 1。您希望整个子字符串不超过句点。