Полиморфное преобразование значений перечисления Java в список строк - PullRequest
17 голосов
/ 11 августа 2009

У меня есть несколько вспомогательных методов, которые преобразуют значения enum в список строк, подходящих для отображения элементом HTML <select>. Мне было интересно, если это возможно, чтобы реорганизовать их в один полиморфный метод.

Это пример одного из моих существующих методов:

/**
 * Gets the list of available colours.
 * 
 * @return the list of available colours
 */
public static List<String> getColours() {
  List<String> colours = new ArrayList<String>();

  for (Colour colour : Colour.values()) {
    colours.add(colour.getDisplayValue());  
  }

  return colours;
}

Я все еще довольно новичок в обобщениях Java, поэтому я не уверен, как передать обобщенное перечисление в метод и использовать ли его также в цикле for.

Обратите внимание, что я знаю, что все перечисленные типы будут иметь этот метод getDisplayValue, но, к сожалению, у них нет общего типа, который его определяет (и я не могу представить его), так что я думаю, быть доступным отражательно ...?

Заранее спасибо за любую помощь.

Ответы [ 10 ]

17 голосов
/ 11 августа 2009

с использованием Class # getEnumConstants () просто:

static <T extends Enum<T>> List<String> toStringList(Class<T> clz) {
     try {
        List<String> res = new LinkedList<String>();
        Method getDisplayValue = clz.getMethod("getDisplayValue");

        for (Object e : clz.getEnumConstants()) {
            res.add((String) getDisplayValue.invoke(e));

        }

        return res;
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

это не совсем безопасно для типов, так как вы можете использовать Enum без getDisplayValue метода.

10 голосов
/ 11 августа 2009

Вы можете вставить этот метод в некоторый служебный класс:

public static <T extends Enum<T>> List<String> getDisplayValues(Class<T> enumClass) {
    try {
        T[] items = enumClass.getEnumConstants();
        Method accessor = enumClass.getMethod("getDisplayValue");

        ArrayList<String> names = new ArrayList<String>(items.length);
        for (T item : items)
            names.add(accessor.invoke(item).toString()));

        return names;
    } catch (NoSuchMethodException ex) {
        // Didn't actually implement getDisplayValue().
    } catch (InvocationTargetException ex) {
        // getDisplayValue() threw an exception.
    }
}

Источник: Изучение Enums

3 голосов
/ 11 августа 2009

Этот подход позволяет избежать отражения:

  public static interface Displayer<T> {
    String displayName(T t);
  }

  public static <T> List<String> getDisplayNames(Iterable<? extends T> stuff,
      Displayer<T> displayer) {
    List<String> list = new ArrayList<String>();
    for (T t : stuff) {
      list.add(displayer.displayName(t));
    }
    return list;
  }

... но требует отдельного типа для всего, что вы хотите отобразить:

  enum Foo {
    BAR("BAR"), BAZ("BAZ");
    private final String displayName;

    private Foo(String displayName) {
      this.displayName = displayName;
    }

    public String getDisplayName() {
      return displayName;
    }
  }

  public static void main(String[] args) {
    Displayer<Foo> fooDisplayer = new Displayer<Foo>() {
      public String displayName(Foo foo) {
        return foo.getDisplayName();
      }
    };

    System.out.println(getDisplayNames(Arrays.asList(Foo.BAR, Foo.BAZ),
        fooDisplayer));
  }

В этом случае используется анонимный тип, но это может быть одноэлементный файл без сохранения состояния или что-то подобное.

3 голосов
/ 11 августа 2009

Здесь вы можете сделать две вещи. Первый (более простой и, следовательно, лучший) способ состоит в том, чтобы ваш метод getStrings () взял список некоторого интерфейса и заставил ваши перечисления реализовать этот интерфейс:

public interface DisplayableSelection {
  public String getDisplayValue();
}

private static List<String> getStrings (Collection<DisplayableSelection> things) {
  List<String> retval = new ArrayList<String>();
  for (DisplayableSelection thing : things) {
    retval.add(thing.getDisplayValue());
  }
}

private static List<String> getColours () {
  return getStrings(Colour.values());
}

Если вы действительно заботитесь о том, чтобы тип был Enum, вы также можете использовать тот факт, что все перечисляемые типы автоматически подклассируют встроенный тип Enum. Итак, для вашего примера (заявление об отказе: я думаю это компилируется, но на самом деле не пробовал):

public interface DisplayableEnum {
  public String getDisplayValue();
}

private static <T extends Enum<T> & DisplayableEnum > List<String> getDisplayValues(Class<T> pClass) {
  List<String> retval = new ArrayList<String>();
  for (DisplayableSelection thing : pClass.getEnumConstants()) {
    retval.add(thing.getDisplayValue());
  }
}

private static List<String> getColours () {
  return getStrings(Colour.class);
}

Эта вторая форма может быть полезна, если вы хотите сделать что-то, что конкретно требует перечисления (например, использовать EnumMap или EnumSet по какой-то причине); в противном случае я бы пошел с первым (поскольку с этим методом вы также можете использовать неперечислимые типы или просто подмножество перечисления).

2 голосов
/ 11 августа 2009

Я бы использовал java.util.ResourceBundle с файлом комплекта, который сопоставляется со значениями toString (и, возможно, именем класса) ваших перечислений, поэтому ваш код становится примерно таким:

bundle.getString(enum.getClass().getName() + enum.toString());
1 голос
/ 11 августа 2009

Вот как я бы предложил это сделать:

Сначала вспомогательный метод и статический внутренний класс в служебном классе где-то:

    @SuppressWarnings("unchecked")
    public static <T> T generateProxy(Object realObject, Class<?>... interfaces) {
        return (T) Proxy.newProxyInstance(realObject.getClass().getClassLoader(), interfaces, new SimpleInvocationHandler(realObject));
    }


    private static class SimpleInvocationHandler implements InvocationHandler {
        private Object invokee;

        public SimpleInvocationHandler(Object invokee) {
            this.invokee = invokee;
        }

        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            method = invokee.getClass().getMethod(method.getName(), method.getParameterTypes());
            if (!method.isAccessible()) {
                method.setAccessible(true);
            }
            try {
                return method.invoke(invokee, args);
            } catch (InvocationTargetException e) {
                throw e.getTargetException();
            }
        }
    }

А затем сложите это вместе со своим перечислением:

   interface DisplayableEnum {
       String getDisplayValue();
   }

private static List<String> getFromEnum(Class<? extends Enum<?>> displayableEnum) {
    List<String> ret = new ArrayList<String>();
    for (Enum e : displayableEnum.getEnumConstants()) {
        DisplayableEnum de = generateProxy(e, DisplayableEnum.class);
        ret.add(de.getDisplayValue());
    }
    return ret;
}

Если производительность вызывает проблему при генерации такого количества прокси-объектов, я бы пошел по пути создания изменяемого класса, который реализует DisplayableEnum, который может изменяться с каждой константой enum (своего рода шаблоном flyweight) и иметь там обработчик вызовов он более гибок в отношении своего реального объекта и вызывает правильный при каждом проходе через цикл.

0 голосов
/ 13 апреля 2011

Давай, ребята .. это не так сложно. Я использую сравнение строк ... но вы можете просто сравнить тип объекта, если хотите.

public static <T extends Enum<T>> Map<T, String>  Initialize_Map(Class<T> enumClass) {
  Map<T, String> map = new HashMap<T, String>();
  for (T val : enumClass.getEnumConstants()) {
    map.put(val, val.toString() + (val.toString().equals("ENUM_ELMT") ? " (appended)" : ""));
  }
  return map;       
}
0 голосов
/ 03 февраля 2011

Я отредактировал таким способом метод первого ответа, и он работает без проблем и без какого-либо интерфейса

public static <T extends Enum<T>> List<String> getDisplayValues(
        Class<T> enumClass) {
    try {
        T[] items = enumClass.getEnumConstants();
        Method accessor = enumClass.getMethod("toString");

        ArrayList<String> names = new ArrayList<String>(items.length);
        for (T item : items)
            names.add(accessor.invoke(item).toString());

        return names;
    } catch (NoSuchMethodException ex) {
        // Didn't actually implement getDisplayValue().
        Log.e(TAG, "getDisplayValues [" + ex+"]");
    } catch (InvocationTargetException ex) {
        // getDisplayValue() threw an exception.
        Log.e(TAG, "getDisplayValues [" + ex+"]");
    } catch (IllegalAccessException ex) {
        // getDisplayValue() threw an exception.
        Log.e(TAG, "getDisplayValues [" + ex+"]");
    }
    return null;
}
0 голосов
/ 11 августа 2009

Обратите внимание, что я знаю, что перечисления в вопрос будет все это метод getDisplayValue, но к сожалению, они не разделяют общий тип, который определяет его (и я не могу представить один), так что я думаю, что нужно будет получить доступ рефлекторно ...

Вы правильно угадываете. Альтернативой может быть, чтобы все перечисления реализовали toString(), возвращая отображаемое значение - но если вы не можете заставить их реализовать интерфейс, то я полагаю, что это также невозможно.

0 голосов
/ 11 августа 2009

(извините, это C #. Я не видел, чтобы вопрос касался Java.)

public string[] GetValues<T>()
{
    return Enum.GetNames(typeof(T));
}

Конечно, для Java все типы enum все еще наследуются от java.util.Enum, поэтому вы можете написать:

public string[] getValues<T extends Enum<T>>()
{
    // use reflection on T.class
}

Поскольку java.util.Enum на самом деле не реализует значения (), я думаю, что рефлексия - единственный путь.

...