Полиморфно преобразовать значения перечисления Java в список строк
У меня есть несколько вспомогательных методов, которые конвертируют значения перечисления в список строк, подходящих для отображения элементом 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
, но, к сожалению, они не имеют общего типа, который его определяет (и я не могу его ввести), поэтому я предполагаю, что это будет иметь для доступа отражаются...?
Заранее благодарим за помощь.
Ответы
Ответ 1
с помощью КласС# 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
.
Ответ 2
Вы можете придерживаться этого метода в каком-то классе утилиты:
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.
}
}
Источник: Рассмотрение перечислений
Ответ 3
Здесь вы можете сделать две вещи. Первый (более простой и, следовательно, лучший) способ состоял бы в том, чтобы ваш метод 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 по какой-либо причине); в противном случае я бы пошел с первым (так как с помощью этого метода вы также можете использовать неперечисленные типы или просто подмножество перечисления).
Ответ 4
Этот подход позволяет избежать отражения:
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));
}
В этом случае используется анонимный тип, но он может быть одиночным синглэтом или сопутствующим.
Ответ 5
Я бы использовал java.util.ResourceBundle
с файлом пакета, который сопоставляется с значениями toString
(и, возможно, класса) ваших перечислений, поэтому ваш код будет выглядеть примерно так:
bundle.getString(enum.getClass().getName() + enum.toString());
Ответ 6
Вот как я хотел бы предложить об этом:
Сначала вспомогательный метод и статический внутренний класс в каком-либо служебном классе:
@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, который может меняться с каждой константой перечисления (вроде мухи-паттерна) и иметь обработчик вызова что более гибко относится к его реальному объекту и вызывает правый на каждом проходе через цикл.
Ответ 7
Обратите внимание, что я знаю, что перечисления в вопрос будет иметь getDisplayValue, но к сожалению, они не разделяют общий тип, который определяет его (и я не может ввести один), поэтому я предполагаю, что должен быть доступен рефлекторно...?
Вы догадываетесь правильно. Альтернативой могло бы быть то, что все перечисления реализуют toString()
, возвращая отображаемое значение, но если вы не можете реализовать их интерфейс, я полагаю, что это невозможно.
Ответ 8
Я отредактировал таким образом метод при первом ответе и работает без проблем и без реализации какого-либо интерфейса
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;
}
Ответ 9
(извините, это С#. Я не видел, что вопрос был для Java.)
public string[] GetValues<T>()
{
return Enum.GetNames(typeof(T));
}
Для Java, конечно, все типы перечислений все еще наследуются от java.util.Enum, поэтому вы можете написать:
public string[] getValues<T extends Enum<T>>()
{
// use reflection on T.class
}
Так как java.util.Enum фактически не реализует значения(), я думаю, что отражение - единственный способ пойти.
Ответ 10
Давай, ребята, это не так сложно. Im использует сравнение строк.. но вы можете просто сравнить тип объекта, если хотите.
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;
}