Используйте универсальные перечислимые классы в картах - PullRequest
4 голосов
/ 13 мая 2019

У меня есть карта имен классов к их перечисляемому классу, и у меня есть метод, который анализирует строку типа "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) «вызов», и на самом деле это из-за этого универсального типа. Если я удалю его в parse, он не скомпилируется, но предупреждение исчезнет. Есть ли хороший способ обойти это?

Ответы [ 2 ]

4 голосов
/ 14 мая 2019

Если у вас есть перечисления, такие как:

  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.Как правило, рекомендуется применять подавление предупреждений в максимально ограниченной области.В этом случае, это для этого единственного назначения.Хотя в общем случае это может быть красный флаг, в данном случае мы знаем, что единственными значениями на карте являются, фактически, классы enum, поскольку они должны быть добавлены с помощью 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]

Вы заметите, что я разбил parseUnsafely и parse на отдельные методы.Причина, по которой мы не хотим раскрывать parseUnsafely напрямую, заключается в том, что по типу возврата он дает гарантию, которую мы на самом деле не можем применить.Если бы он был выставлен, то мы могли бы написать код, подобный

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

, который компилируется, но завершается с ошибкой во время выполнения с исключением класса приведения.

1 голос
/ 14 мая 2019

Не существует безопасного способа иметь значения 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, ?>>. Каждое значение представляет собой карту констант enum с ключом-именем ? достаточно; помнить, что постоянные значения являются перечислениями, нет смысла, поскольку parse возвращает Object[].
  • registerEnum имеет универсальный тип, чтобы гарантировать, что его аргумент является допустимым типом перечисления. Вместо того, чтобы хранить аргумент класса, он сохраняет константы этого перечисления.
  • parse не нуждается в универсальном типе, поскольку возвращает Object[].
  • parse не использует какие-либо методы Enum, поэтому безопасность универсальных типов больше не является проблемой.
  • Я исправил ошибку: strParameter.substring(0, delim); вместо delim - 1. Вы хотите, чтобы вся подстрока была до, но не включая период.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...